[
  {
    "path": ".circleci/config.yml",
    "content": "version: 2\njobs:\n  build:\n    working_directory: ~/impress.js\n    docker:\n      - image: circleci/node:current-browsers\n    steps:\n      - checkout\n      - restore_cache:\n          key: dependency-cache-{{ checksum \"package.json\" }}\n      - run:\n          name: install-npm\n          command: npm install\n      - save_cache:\n          key: dependency-cache-{{ checksum \"package.json\" }}\n          paths:\n            - ./node_modules\n      - run:\n          name: build\n          command: npm run build\n      - run:\n          name: lint\n          command: npm run lint\n      - run:\n          name: test\n          command: npm test\n"
  },
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n    \"env\": {\n        \"browser\": true,\n        \"es6\": true\n    },\n    \"extends\": \"eslint:recommended\",\n    \"globals\": {\n        \"Atomics\": \"readonly\",\n        \"SharedArrayBuffer\": \"readonly\"\n    },\n    \"parserOptions\": {\n        \"ecmaVersion\": 2018\n    },\n    \"rules\": {\n        \"accessor-pairs\": \"error\",\n        \"array-bracket-newline\": \"error\",\n        \"array-bracket-spacing\": \"error\",\n        \"array-callback-return\": \"error\",\n        \"array-element-newline\": \"error\",\n        \"arrow-body-style\": \"error\",\n        \"arrow-parens\": \"error\",\n        \"arrow-spacing\": \"error\",\n        \"block-scoped-var\": \"off\",\n        \"block-spacing\": [\n            \"error\",\n            \"always\"\n        ],\n        \"brace-style\": [\n            \"error\",\n            \"1tbs\",\n            {\n                \"allowSingleLine\": true\n            }\n        ],\n        \"callback-return\": \"error\",\n        \"camelcase\": \"error\",\n        \"capitalized-comments\": \"off\",\n        \"class-methods-use-this\": \"error\",\n        \"comma-dangle\": \"error\",\n        \"comma-spacing\": [\n            \"error\",\n            {\n                \"after\": true,\n                \"before\": false\n            }\n        ],\n        \"comma-style\": [\n            \"error\",\n            \"last\"\n        ],\n        \"complexity\": \"error\",\n        \"computed-property-spacing\": [\n            \"error\",\n            \"always\"\n        ],\n        \"consistent-return\": \"error\",\n        \"consistent-this\": \"off\",\n        \"curly\": \"error\",\n        \"default-case\": \"error\",\n        \"dot-location\": \"error\",\n        \"dot-notation\": \"error\",\n        \"eol-last\": \"error\",\n        \"eqeqeq\": \"error\",\n        \"func-call-spacing\": \"error\",\n        \"func-name-matching\": \"error\",\n        \"func-names\": \"off\",\n        \"func-style\": [\n            \"error\",\n            \"expression\"\n        ],\n        \"function-paren-newline\": \"error\",\n        \"generator-star-spacing\": \"error\",\n        \"global-require\": \"error\",\n        \"guard-for-in\": \"error\",\n        \"handle-callback-err\": \"error\",\n        \"id-blacklist\": \"error\",\n        \"id-length\": \"off\",\n        \"id-match\": \"error\",\n        \"implicit-arrow-linebreak\": \"error\",\n        \"indent\": \"error\",\n        \"indent-legacy\": \"error\",\n        \"init-declarations\": \"off\",\n        \"jsx-quotes\": \"error\",\n        \"key-spacing\": \"off\",\n        \"keyword-spacing\": [\n            \"error\",\n            {\n                \"after\": true,\n                \"before\": true\n            }\n        ],\n        \"line-comment-position\": \"off\",\n        \"linebreak-style\": [\n            \"error\",\n            \"unix\"\n        ],\n        \"lines-around-comment\": \"error\",\n        \"lines-around-directive\": \"off\",\n        \"lines-between-class-members\": \"error\",\n        \"max-classes-per-file\": \"error\",\n        \"max-depth\": \"error\",\n        \"max-len\": \"off\",\n        \"max-lines\": \"error\",\n        \"max-lines-per-function\": \"off\",\n        \"max-nested-callbacks\": \"error\",\n        \"max-params\": \"error\",\n        \"max-statements\": \"off\",\n        \"max-statements-per-line\": \"off\",\n        \"multiline-comment-style\": [\n            \"error\",\n            \"separate-lines\"\n        ],\n        \"new-cap\": \"error\",\n        \"new-parens\": \"error\",\n        \"newline-after-var\": \"off\",\n        \"newline-before-return\": \"off\",\n        \"newline-per-chained-call\": \"error\",\n        \"no-alert\": \"error\",\n        \"no-array-constructor\": \"error\",\n        \"no-async-promise-executor\": \"error\",\n        \"no-await-in-loop\": \"error\",\n        \"no-bitwise\": \"error\",\n        \"no-buffer-constructor\": \"error\",\n        \"no-caller\": \"error\",\n        \"no-catch-shadow\": \"error\",\n        \"no-confusing-arrow\": \"error\",\n        \"no-continue\": \"error\",\n        \"no-div-regex\": \"error\",\n        \"no-duplicate-imports\": \"error\",\n        \"no-else-return\": \"error\",\n        \"no-empty-function\": \"error\",\n        \"no-eq-null\": \"error\",\n        \"no-eval\": \"error\",\n        \"no-extend-native\": \"error\",\n        \"no-extra-bind\": \"error\",\n        \"no-extra-label\": \"error\",\n        \"no-extra-parens\": \"off\",\n        \"no-floating-decimal\": \"error\",\n        \"no-implicit-coercion\": \"error\",\n        \"no-implicit-globals\": \"error\",\n        \"no-implied-eval\": \"error\",\n        \"no-inline-comments\": \"off\",\n        \"no-inner-declarations\": [\n            \"error\",\n            \"functions\"\n        ],\n        \"no-invalid-this\": \"off\",\n        \"no-iterator\": \"error\",\n        \"no-label-var\": \"error\",\n        \"no-labels\": \"error\",\n        \"no-lone-blocks\": \"error\",\n        \"no-lonely-if\": \"error\",\n        \"no-loop-func\": \"error\",\n        \"no-magic-numbers\": \"off\",\n        \"no-misleading-character-class\": \"error\",\n        \"no-mixed-operators\": \"error\",\n        \"no-mixed-requires\": \"error\",\n        \"no-multi-assign\": \"error\",\n        \"no-multi-spaces\": \"off\",\n        \"no-multi-str\": \"error\",\n        \"no-multiple-empty-lines\": \"error\",\n        \"no-native-reassign\": \"error\",\n        \"no-negated-condition\": \"off\",\n        \"no-negated-in-lhs\": \"error\",\n        \"no-nested-ternary\": \"error\",\n        \"no-new\": \"error\",\n        \"no-new-func\": \"error\",\n        \"no-new-object\": \"error\",\n        \"no-new-require\": \"error\",\n        \"no-new-wrappers\": \"error\",\n        \"no-octal-escape\": \"error\",\n        \"no-param-reassign\": \"off\",\n        \"no-path-concat\": \"error\",\n        \"no-plusplus\": \"off\",\n        \"no-process-env\": \"error\",\n        \"no-process-exit\": \"error\",\n        \"no-proto\": \"error\",\n        \"no-prototype-builtins\": \"error\",\n        \"no-restricted-globals\": \"error\",\n        \"no-restricted-imports\": \"error\",\n        \"no-restricted-modules\": \"error\",\n        \"no-restricted-properties\": \"error\",\n        \"no-restricted-syntax\": \"error\",\n        \"no-return-assign\": \"error\",\n        \"no-return-await\": \"error\",\n        \"no-script-url\": \"error\",\n        \"no-self-compare\": \"error\",\n        \"no-sequences\": \"error\",\n        \"no-shadow\": \"off\",\n        \"no-shadow-restricted-names\": \"error\",\n        \"no-spaced-func\": \"error\",\n        \"no-sync\": \"error\",\n        \"no-tabs\": \"error\",\n        \"no-template-curly-in-string\": \"error\",\n        \"no-ternary\": \"off\",\n        \"no-throw-literal\": \"error\",\n        \"no-trailing-spaces\": \"error\",\n        \"no-undef-init\": \"error\",\n        \"no-undefined\": \"off\",\n        \"no-underscore-dangle\": \"error\",\n        \"no-unmodified-loop-condition\": \"error\",\n        \"no-unneeded-ternary\": \"error\",\n        \"no-unused-expressions\": \"error\",\n        \"no-use-before-define\": \"off\",\n        \"no-useless-call\": \"error\",\n        \"no-useless-catch\": \"error\",\n        \"no-useless-computed-key\": \"error\",\n        \"no-useless-concat\": \"error\",\n        \"no-useless-constructor\": \"error\",\n        \"no-useless-rename\": \"error\",\n        \"no-useless-return\": \"error\",\n        \"no-var\": \"off\",\n        \"no-void\": \"error\",\n        \"no-warning-comments\": \"error\",\n        \"no-whitespace-before-property\": \"error\",\n        \"no-with\": \"error\",\n        \"nonblock-statement-body-position\": \"error\",\n        \"object-curly-newline\": \"error\",\n        \"object-curly-spacing\": [\n            \"error\",\n            \"always\"\n        ],\n        \"object-shorthand\": \"off\",\n        \"one-var\": \"off\",\n        \"one-var-declaration-per-line\": \"off\",\n        \"operator-assignment\": \"error\",\n        \"operator-linebreak\": \"error\",\n        \"padded-blocks\": \"off\",\n        \"padding-line-between-statements\": \"error\",\n        \"prefer-arrow-callback\": \"off\",\n        \"prefer-const\": \"error\",\n        \"prefer-destructuring\": \"off\",\n        \"prefer-numeric-literals\": \"error\",\n        \"prefer-object-spread\": \"error\",\n        \"prefer-promise-reject-errors\": \"error\",\n        \"prefer-reflect\": \"off\",\n        \"prefer-rest-params\": \"off\",\n        \"prefer-spread\": \"error\",\n        \"prefer-template\": \"off\",\n        \"quote-props\": \"off\",\n        \"quotes\": [\n            \"error\",\n            \"double\"\n        ],\n        \"radix\": \"error\",\n        \"require-atomic-updates\": \"error\",\n        \"require-await\": \"error\",\n        \"require-jsdoc\": \"error\",\n        \"require-unicode-regexp\": \"off\",\n        \"rest-spread-spacing\": \"error\",\n        \"semi\": \"error\",\n        \"semi-spacing\": [\n            \"error\",\n            {\n                \"after\": true,\n                \"before\": false\n            }\n        ],\n        \"semi-style\": [\n            \"error\",\n            \"last\"\n        ],\n        \"sort-imports\": \"error\",\n        \"sort-keys\": \"off\",\n        \"sort-vars\": \"off\",\n        \"space-before-blocks\": \"error\",\n        \"space-before-function-paren\": \"off\",\n        \"space-in-parens\": [\n            \"error\",\n            \"always\"\n        ],\n        \"space-infix-ops\": \"error\",\n        \"space-unary-ops\": \"error\",\n        \"spaced-comment\": [\n            \"error\",\n            \"always\"\n        ],\n        \"strict\": \"error\",\n        \"switch-colon-spacing\": \"error\",\n        \"symbol-description\": \"error\",\n        \"template-curly-spacing\": \"error\",\n        \"template-tag-spacing\": \"error\",\n        \"unicode-bom\": [\n            \"error\",\n            \"never\"\n        ],\n        \"valid-jsdoc\": \"error\",\n        \"vars-on-top\": \"off\",\n        \"wrap-regex\": \"error\",\n        \"yield-star-spacing\": \"error\",\n        \"yoda\": [\n            \"error\",\n            \"never\"\n        ]\n    }\n};\n"
  },
  {
    "path": ".gitattributes",
    "content": "* text=auto\n"
  },
  {
    "path": ".github/workflows/pipeline.yaml",
    "content": "name: Automates the Verification, Testing, and Building Process\n\nenv:\n  NPM_CONFIG_LOGLEVEL: \"warn\"\n  NPM_CONFIG_PREFER_OFFLINE: \"true\"\n\njobs:\n  verification:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Check out repository\n        uses: actions/checkout@v4\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: 22.x\n      - name: Install dependencies\n        run: npm clean-install\n      - name: Lint code\n        run: npm run lint\n\n  testing:\n    needs: verification\n    runs-on: ubuntu-latest\n    steps:\n      - name: Check out repository\n        uses: actions/checkout@v4\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: 22.x\n      - name: Install dependencies\n        run: npm clean-install\n      - name: Run test suite\n        run: npm run test\n\n  assembly:\n    needs: testing\n    runs-on: ubuntu-latest\n    steps:\n      - name: Check out repository\n        uses: actions/checkout@v4\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: 22.x\n      - name: Install dependencies\n        run: npm clean-install\n      - name: Build artifact(s)\n        run: npm run build\n\non:\n  pull_request:\n    branches:\n      - master\n  push:\n    branches:\n      - master\n\npermissions:\n  contents: read\n"
  },
  {
    "path": ".gitignore",
    "content": "/js/impress.min.js.map\n/js/impress.min.js\n/node_modules\n/npm-debug.log\n/*.tgz\n\n# Files for editors and other tools\n/.brackets.json\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"extras\"]\n\tpath = extras\n\turl = https://github.com/impress/impress-extras\n"
  },
  {
    "path": ".jscsrc",
    "content": "{\n\t\"preset\": \"jquery\",\n        // Since we check quotemarks already in jshint, this can be turned off\n        \"validateQuoteMarks\": false\n}\n"
  },
  {
    "path": ".jshintrc",
    "content": "{\n\t\"globals\": {\n\t\t\"module\": true\n\t},\n\t\"boss\": true,\n\t\"browser\": true,\n\t\"curly\": true,\n        \"esversion\": 6,\n\t\"eqeqeq\": true,\n\t\"eqnull\": true,\n\t\"expr\": true,\n\t\"immed\": true,\n\t\"noarg\": true,\n\t\"quotmark\": \"double\",\n\t\"undef\": true,\n\t\"unused\": true\n}\n"
  },
  {
    "path": ".npmignore",
    "content": "/.*/\n/.*\n/examples\n/extras/*\n/circle.yml\n"
  },
  {
    "path": "DOCUMENTATION.md",
    "content": "# Reference API\n\n## HTML\n\n### Root Element\n\nimpress.js requires a Root Element. All the content of the presentation will be created inside that element. It is not recommended to manipulate any of the styles, attributes or classes that are created by impress.js inside the Root Element after initialization.\n\nTo change the duration of the transition between slides use `data-transition-duration=\"2000\"` giving it\na number of ms. It defaults to 1000 (1s).\n\nWhen authoring impress.js presentations, you should target some screen size, which you can define here.\nThe default is 1024 x 768. You should write your CSS as if this is the screen size used for the\npresentation. When you present your presentation on a screen (or browser window) of different size,\nimpress.js will automatically scale the presentation to fit the screen. The minimum and maximum limits\nto this scaling can also be defined here.\n\nAll impress.js steps are wrapped inside a div element of 0 size! This means that in your CSS you\ncan't use relative values for width and height (example: `width: 100%`) to define the size of step elements.\nYou need to use pixel values. The pixel values used here correspond to the `data-width` and `data-height`\ngiven to the `#impress` root element. When the presentation is viewed on a larger or smaller screen, impress.js\nwill automatically scale the steps to fit the screen.\n\n**NOTE:** The default width and height have been changed to target HD screens in v1.2.0. If you\ndon't set target width and height explicitly, layout and dimensions of your presentations are likely\nto be affected. In order to get back the old target resolution, use:\n\n    <div id=\"impress\" data-width=\"1024\" data-height=\"768\" data-max-scale=\"1\" data-min-scale=\"0\"\n\n\nYou can also control the perspective with `data-perspective=\"500\"` giving it a number of pixels.\nIt defaults to 1000. You can set it to 0 if you don't want any 3D effects.\nIf you are willing to change this value make sure you understand how CSS perspective works:\nhttps://developer.mozilla.org/en/CSS/perspective\n\nSee also [the plugin README](src/plugins/README.md) for documentation on `data-autoplay`.\n\n**Attributes**\n\nAttribute                | Default   | Explanation\n-------------------------|-----------|------------\ndata-transition-duration | 1000 (ms) | Speed of transition between steps.\ndata-width               | 1920 (px) | Width of target screen size. When presentation is viewed on a larger or smaller screen, impress.js will scale all content automatically.\ndata-height              | 1080 (px) | Height of target screen size.\ndata-max-scale           | 3         | Maximum scale factor. (Note that the default 1 will not increase content size on larger screens!)\ndata-min-scale           | 0         | Minimum scale factor.\ndata-perspective         | 1000      | Perspective for 3D rendering. See https://developer.mozilla.org/en/CSS/perspective\n\n**Example:**\n\n```html\n<div id=\"impress\"\n    data-transition-duration=\"1000\"\n\n    data-width=\"1024\"\n    data-height=\"768\"\n    data-max-scale=\"3\"\n    data-min-scale=\"0\"\n    data-perspective=\"1000\"\n\n    data-autoplay=\"7\">\n```\n\n### Step Element\n\nA Step Element is an element that contains metadata that defines how it is going to be presented in the screen.\nA Step Element should contain a `.step` class and an optional `id` attribute.\nThe content represents an html fragment that will be positioned at the center of the camera.\nIn the Step Element, you can define a specific set of default attributes and positioning, that are documented below.\n\n**Example:**\n\n```html\n<div id=\"bored\" class=\"step\" data-x=\"-1000\">\n    <q>Aren’t you just <b>bored</b> with all those slides-based presentations?</q>\n</div>\n```\n\n#### 2D Coordinates Positioning (data-x, data-y)\n\nDefine the pixel based position in which the **center** of the [Step Element](#step-element) will be positioned inside the infinite canvas.\n\n**Attributes**\n\nAttribute                | Default   | Explanation\n-------------------------|-----------|------------\ndata-x                   | 0         | X coordinate for step position\ndata-y                   | 0         | Y coordinate for step position\n\n**Example:**\n\n```html\n<div id=\"bored\" class=\"step\" data-x=\"-1000\" data-y=\"-1500\">\n    <q>Aren’t you just <b>bored</b> with all those slides-based presentations?</q>\n</div>\n```\n\n#### 2D Scaling (data-scale)\n\nDefines the scaling multiplier of the [Step Element](#step-element) relative to other Step Elements. For example, `data-scale=\"4\"` means that the element will appear to be 4 times larger than the others. From the presentation and transitions point of view, it means that it will have to be scaled down (4 times) to make it back to its correct size.\n\n**Example:**\n\n```html\n<div id=\"title\" class=\"step\" data-x=\"0\" data-y=\"0\" data-scale=\"4\">\n    <span class=\"try\">then you should try</span>\n    <h1>impress.js<sup>*</sup></h1>\n    <span class=\"footnote\"><sup>*</sup> no rhyme intended</span>\n</div>\n```\n\n#### 2D Rotation (data-rotate)\n\nRepresents the amount of clockwise rotation of the element relative to 360 degrees.\n\n**Example:**\n\n```html\n<div id=\"its\" class=\"step\" data-x=\"850\" data-y=\"3000\" data-rotate=\"90\" data-scale=\"5\">\n    <p>\n      It’s a <strong>presentation tool</strong> <br>\n      inspired by the idea behind <a href=\"http://prezi.com\">prezi.com</a> <br>\n      and based on the <strong>power of CSS3 transforms and transitions</strong> in modern browsers.\n    </p>\n</div>\n```\n\n\n#### 3D Coordinates Positioning (data-z)\n\nDefine the pixel based position in which the **center** of the [Step Element](#step-element) will be positioned inside the infinite canvas on the third dimension (Z) axis. For example, if we use `data-z=\"-3000\"`, it means that the [Step Element](#step-element) will be positioned far away from the camera (by 3000px).\n\n**Example:**\n\n```html\n<div id=\"tiny\" class=\"step\" data-x=\"2825\" data-y=\"2325\" data-z=\"-3000\" data-rotate=\"300\" data-scale=\"1\">\n    <p>and <b>tiny</b> ideas</p>\n</div>\n```\n\n**Note:** The introduction of the [rel](src/plugins/rel/README.md) plugin includes a slight backward incompatible change.\nPreviously the default value for `data-x`, `data-y` and `data-z` was zero. The `rel` plugin changes the default to inherit\nthe value of the previous slide. This means, you need to explicitly set these values to zero, if they ever were non-zero.\n\n\n#### 3D Rotation (data-rotate-x, data-rotate-y, data-rotate-z)\n\nYou can not only position a [Step Element](#step-element) in 3D, but also rotate it around any axis.\n\n**Example:**\n\nThe example below will get rotated by -40 degrees (40 degrees anticlockwise) around X axis and 10 degrees (clockwise) around Y axis.\n\nYou can of course rotate it around Z axis with `data-rotate-z` - it has exactly the same effect as `data-rotate` (these two are basically aliases).\n\n```HTML\n<div id=\"its-in-3d\" class=\"step\" data-x=\"6200\" data-y=\"4300\" data-z=\"-100\" data-rotate-x=\"-40\" data-rotate-y=\"10\" data-scale=\"2\">\n    <p>\n      <span class=\"have\">have</span>\n      <span class=\"you\">you</span>\n      <span class=\"noticed\">noticed</span>\n      <span class=\"its\">it’s</span>\n      <span class=\"in\">in</span>\n      <b>3D<sup>*</sup></b>?\n    </p>\n    <span class=\"footnote\">* beat that, prezi ;)</span>\n</div>\n```\n\n#### 3D Rotation Order (data-rotate-order)\n\nThe order in which the CSS `rotateX(), rotateY(), rotateZ()` transforms are applied matters. This is because each rotation is relative to the then current position of the element.\n\nBy default the rotation order is `data-rotate-order=\"xyz\"`. For some advanced uses you may need to change it. The demo presentation [3D rotations](examples/3D-rotations/index.html) sets this attribute to rotate some steps into positions that are impossible to reach with the default order.\n\n\n## CSS\n\n### 4D States (.past, .present and .future classes)\n\nThe `.future` class is added to all [Step Elements](#step-element) that haven't been visited yet.\n\n**Example:**\n\n```CSS\n.future {\n  display: none;\n}\n```\n\nThe `.present` class is added to the [Step Element](#step-element) that is currently at the center of the camera. This is useful to create animations inside the step once the camera navigates to it.\n\n**Example:**\n\n```CSS\n.present .rotating {\n  transform: rotate(-10deg);\n  transition-delay: 0.25s;\n}\n```\n\nThe `.past` class is added to all [Step Elements](#step-element) that have been visited at least once.\n\n**Example:**\n\n```CSS\n.past {\n  display: none;\n}\n```\n\n### Current Active Step (.active class)\n\nThe `.active` class is added to the [Step Element](#step-element) that is currently visible at the center of the camera.\n\n**Example:**\n\n```CSS\n.step {\n  opacity: 0.3;\n  transition: opacity 1s;\n}\n.step.active {\n  opacity: 1\n}\n```\n\nAt the same time, the `impress-on-*` class is added to the body element, the class name represents the active [Step Element](#step-element) id. This allows for custom global styling, since you can't match a CSS class backwards from the active [Step Element](#step-element) to the `body`.\n\n**Example:**\n\n```CSS\n.impress-on-overview .step {\n    opacity: 1;\n    cursor: pointer;\n}\n.impress-on-step-1,\n.impress-on-step-2,\n.impress-on-step-3 {\n  background: LightBlue;\n}\n```\n\n### Progressive Enhancement (.impress-not-supported class)\n\nThe `.impress-not-supported` class is added to the `body` element if the browser doesn't support the features required by impress.js to work, it is useful to apply some fallback styles in the CSS.\n\nIt's not necessary to add it manually on the `body` element. If the script detects that the browser lacks important features it will add this class.\n\nIt is recommended to add the class manually to the `body` element though, because that means that users without JavaScript will also get fallback styles. When impress.js script detects that the browser supports all required features, the `.impress-not-support` class will be removed from the `body` element.\n\n**Example:**\n\n```CSS\n.impress-not-supported .step {\n  display: inline-block;\n}\n```\n\n## Plugins\n\nMany new features are implemented as plugins. The [Plugins documentation](src/plugins/README.md) is the starting place to learn about those, as well as the README.md of [each plugin](src/plugins/).\n\n\n## JavaScript\n\n### impress( [ id ] )\n\nA factory function that creates the [ImpressAPI](#impressapi).\n\nAccepts a [`String`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) that represents the id of the root element in the page. If omitted, impress.js will lookup for the element with the id \"impress\" by default.\n\n**Example:**\n\n```JavaScript\nvar impressAPI = impress( \"root\" );\n```\n\n### ImpressAPI\n\nThe main impress.js API that handles common operations of impress.js, listed below.\n\n#### .init()\n\nInitializes impress.js globally in the page. Only one instance of impress.js is supported per document.\n\n**Example:**\n\n```JavaScript\nimpress().init();\n```\n\nTriggers the `impress:init` event in the [Root Element](#root-element) after the presentation is initialized.\n\n**Example:**\n\n```JavaScript\nvar rootElement = document.getElementById( \"impress\" );\nrootElement.addEventListener( \"impress:init\", function() {\n  console.log( \"Impress init\" );\n});\nimpress().init();\n```\n\n#### .tear()\n\nResets the DOM to its original state, as it was before `init()` was called.\n\nThis can be used to \"unload\" impress.js. A particular use case for this is, if you want to do\ndynamic changes to the presentation, you can do a teardown, apply changes, then call `init()`\nagain. (In most cases, this will not cause flickering or other visible effects to the user,\nbeyond the intended dynamic changes.)\n\n**Example:**\n\n```JavaScript\nimpress().tear();\n```\n\n#### .next()\n\nNavigates to the next step of the presentation using the [`goto()` function](#impressgotostepindexstepelementidstepelement-duration).\n\n**Example:**\n\n```JavaScript\nvar api = impress();\napi.init();\napi.next();\n```\n\n#### .prev()\n\nNavigates to the previous step of the presentation using the [`goto()` function](#impressgotostepindexstepelementidstepelement-duration).\n\n**Example:**\n\n```JavaScript\nvar api = impress();\napi.init();\napi.prev();\n```\n\n#### .goto( stepIndex | stepElementId | stepElement, [ duration ] )\n\nAccepts a [`Number`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) that represents the step index.\n\nNavigates to the step given the provided step index.\n\n**Example:**\n\n```JavaScript\nvar api = impress();\napi.init();\napi.goto(7);\n```\n\nAccepts a [`String`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) that represents the [Step Element](#step-element) id.\n\nNavigates to the step given the provided [Step Element](#step-element) id.\n\n**Example:**\n\n```JavaScript\nvar api = impress();\napi.init();\napi.goto( \"overview\" );\n```\n\nAccepts an [`HTMLElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement) that represents the [Step Element](#step-element).\n\nNavigates to the step given the provided [Step Element](#step-element).\n\n**Example:**\n\n```JavaScript\nvar overview = document.getElementById( \"overview\" );\nvar api = impress();\napi.init();\napi.goto( overview );\n```\n\nAccepts an optional [Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) in the last argument that represents the duration of the transition in milliseconds. If not provided, the default transition duration for the presentation will be used.\n\nTriggers the `impress:stepenter` event in the [Root Element](#root-element) when the presentation navigates to the target [Step Element](#step-element).\n\n**Example:**\n\n```JavaScript\nvar rootElement = document.getElementById( \"impress\" );\nrootElement.addEventListener( \"impress:stepenter\", function(event) {\n  var currentStep = event.target;\n  console.log( \"Entered the Step Element '\" + currentStep.id + \"'\" );\n});\n```\n\nTriggers the `impress:stepleave` event in the [Root Element](#root-element) when the presentation navigates away from the current [Step Element](#step-element).\n\n**Example:**\n```JavaScript\nvar rootElement = document.getElementById( \"impress\" );\nrootElement.addEventListener( \"impress:stepleave\", function(event) {\n  var currentStep = event.target;\n  var nextStep = event.detail.next;\n  console.log( \"Left the Step Element '\" + currentStep.id + \"' and about to enter '\" + nextStep.id );\n});\n```\n\n# Improve The Docs\n\nDid you find something that can be improved? Then [create an issue](https://github.com/impress/impress.js/issues/new) so that we can discuss it!\n"
  },
  {
    "path": "GettingStarted.md",
    "content": "# Introduction\nWelcome to impress.js! This presentation framework allows you to create stunning presentations with the power of CSS3 transformations.\n**NOTE:** This Guide is not made for you if you have never written HTML and/or CSS before. Knowing your way around in JavaScript certainly helps, but it is not a necessity. You may still continue this tutorial and try to understand what we do as you go. \n\nFor more advanced and complete documentation, you might prefer the [DOCUMENTATION](DOCUMENTATION.md).\n\n# Getting started with impress.js\n## Installation / acquiring the framework\nFirst of all, you need to know if you are going to have a WiFi connection when you hold your presentation. If you are not sure, please use the method where you download the file instead of the CDN.\n\n### Including from CDN\nLoading the script from the CDN is quite straightforward. If you copy the example code below, you need to do nothing else; impress.js will be loaded automatically.\n\n**Direct links to different versions of the impress.js file**\n- V2.0.0: https://cdn.jsdelivr.net/gh/impress/impress.js@2.0.0/js/impress.js\n- V1.1.0: https://cdn.jsdelivr.net/gh/impress/impress.js@1.1.0/js/impress.js\n- Source: https://cdn.jsdelivr.net/gh/impress/impress.js/js/impress.js\n\n### Download the file to your PC\nHead to the releases tab and download the source code as a zip or a tarball. Go ahead and unzip/untar it. You need to copy the folder */js/* into the folder you are working in. Optionally, to make your life a bit easier, you can also copy the */css/* folder into the same location.\n\n\n## Setting up the project\nOpen up your favorite text editor / IDE, for example, Visual Studio Code, Atom, Notepad++, ...\nNow, create a new file called *index.html* and create the basic HTML structure:\n\n```\n<!DOCTYPE html>\n<html>\n    <head>\n        <title>My first presentation</title>\n        <link rel=\"stylesheet\" href=\"./css/impress-common.css\"><!--Leave out, if you don't use impress-common.css-->\n    </head>\n    <body class=\"impress-not-supported\">\n        <div class=\"fallback-message\">\n            <p>Your browser <b>doesn't support the features required</b> by impress.js, so you are presented with a simplified version of this presentation.</p>\n            <p>For the best experience please use the latest <b>Chrome</b>, <b>Safari</b> or <b>Firefox</b> browser.</p>\n        </div>\n\n        <div id=\"impress\">\n            <div id=\"myFirstSlide\" class=\"step\">\n                <h1>My first Slide</h1>\n            </div>\n        </div>\n\n        <script src=\"js/impress.js\"></script>\n        <script>window.impress || document.write('<script src=\"https://cdn.jsdelivr.net/gh/impress/impress.js@2.0.0/js/impress.js\">\\x3C/script>');</script>\n        <script>impress().init()</script>\n    </body>\n</html>\n```\n\nNow, head into a file manager, navigate to the file you just created (*index.html*), and open it. You should end up in a browser where you should see \"My first Slide\" displayed. Since this isn't exciting, we won't change anything and will review what you just typed. What do these lines mean?\n\nWell, first things first, you should probably give your presentation a title. You may do this in normal HTML fashion by changing the *title* HTML tag.\n\nSo now, we have reached the HTML body. You can see that it already belongs to a class. This class just tells impress.js that this is the body where the \"fallback-message\" should be displayed when it detects, that your browser does not support CSS3 and therefore impress.js won't work. You can easily omit that class though, including the \"fallback-message\" div with its content, if you only intend to use the presentation for yourself and you know about the fact that some browsers might not work.\n\nNow, probably the most important part of all is the *div* that belongs to the ```impress``` class. This *div* should contain all the HTML code you write, as everything outside that class will not be animated by impress.js. \n\nFinally, we load the ```impress.js``` script from your local copy (if you have one) or from the CDN, if you do not have a local copy, and execute \n```\nimpress().init()\n```\nto initialize impress.js. Now, let's continue to explore more features of this amazing tool!\n\n## Creating slides\nCreating slides is fairly easy. You create a *div* that belongs to the class ```step``` and you are off to the races! Let me give you an example:\n```\n<div class=\"step\">\nHello World\n</div>\n```\n\nNow, if you reload the presentation, you start to see a \\*slight\\* problem. All your text is stacked... How do we work around that?\n\nObviously, impress.js has an answer to it. You can add the following additional attributes to your div to make it work:\n\nAttribute       | Explanation\n----------------|------------\ndata-x          | Position the element along the x-axis (from left to right)\ndata-y          | Position the element along the y-axis (from top to bottom)\ndata-z          | Position the element along the z-axis (3D-Effect, move the element behind another one)\ndata-rotate     | Rotate the element (if not using data-z!)\ndata-rotate-x   | Rotate the element along the x-axis\ndata-rotate-y   | Rotate the element along the y-axis\ndata-rotate-z   | Rotate the element along the z-axis\ndata-scale      | Scale the element. \n\nThese are the basic positioning options in impress.js. All of the attributes take Strings as arguments, so be aware that you need to put quotation marks around the numbers! The *data-rotate* attributes take an angle in the form of a String as an argument.\n\nNow that you have created the slides, you might want to style them. This is where CSS comes into play. Add another file to your project called, e.g., ```style.css```. \n\n**NOTE:** Whatever you do, do not mess with the positioning and rotation of the div that belongs to the class *step*, but add a div inside of it, if you really have to mess with these properties. See the example below. Always position *steps* with the *data-* attribute!\n\n```\n<div class=\"step yourClassNameHere\" data-x=\"1000\" data-y=\"1000\" data-z=\"-1000\" data-scale=\"2\" data-rotate-z=\"90\">\n    <div class=\"yourSubClassNameHere\">\n        <h1>Powerful, yet still simple</h1>\n    </div>\n</div>\n```\n**NOTE:** You may also use negative numbers for all these properties!\n\n## More advanced features\nYou might want to change some default settings, like the transition speed, the width & height of the target screen, etc. This table is from the [DOCUMENTATION](DOCUMENTATION.md) and was slightly adapted.\n\nAttribute                | Default   | Explanation\n-------------------------|-----------|------------\ndata-transition-duration | 1000 (ms) | Speed of transition between steps.\ndata-width               | 1920 (px) | Width of target screen size. When presentation is viewed on a larger or smaller screen, impress.js will scale all content automatically.\ndata-height              | 1080 (px) | Height of target screen size.\ndata-max-scale           | 3         | Maximum scale factor. (Note that the default 1 will not increase content size on larger screens!)\ndata-min-scale           | 0         | Minimum scale factor.\ndata-perspective         | 1000      | Perspective for 3D rendering. See https://developer.mozilla.org/en/CSS/perspective\n\n### **Renaming Steps**\nYou can give each step an ID. The name of the ID will be displayed in the browser's navigation bar instead of the default *step-x*, where x is replaced by the current step number. This can be especially helpful when trying to jump between steps and go back to a previous one. If you want to know how to move to a specific slide, you should take a look at the [README](./src/plugins/goto/README.md) of the \"Goto\" plugin. \n\n# Using PLUGINS\nImpress.js is limited to everything we have discussed so far, along with some additional details; we won't cover them here. Check the [DOCUMENTATION](DOCUMENTATION.md) for that.\n\nimpress.js has accumulated a lot of very useful plugins. You may find all of them [here](./src/plugins/)!\n\nEach Plugin has a README.md file, which you may read to get an idea of how to use it. Some plugins run unnoticed in the background, such as the *resize* plugin, which automatically resizes the presentation whenever the browser window changes size. Here, I will give you an overview of some of the plugins that impress.js includes by default. \n\n**NOTE:** As previously mentioned, if you'd like to get more info about how it works, take a look at the [DOCUMENTATION](DOCUMENTATION.md) or the README.md files of the plugins.\n\n## [impressConsole](/src/plugins/impressConsole/README.md)\nThis plugin opens up an additional browser tab that contains a speaker console. There you can see the current slide, the past slide, and your notes. You add notes to your presentation by adding a *div* that belongs to the class \"notes\" to your *div* that belongs to the class \"step\". \n\n### **adding notes to your presentation**\nYou may add notes to your presentation by adding a div of class *notes* into the div of class *step*, like so:\n\n```\n    <div class=\"step\">\n        Some text that is being displayed on your slides\n        <div class=\"notes\">\n            this won't be displayed in your presentation\n        </div>\n    </div>\n```\nNow that you have added the notes to your HTML, it is time to hide them. You need to add the following code to your CSS file (or in the style tag in the header):\n\n```\n    .notes {\n        display: none;\n    }\n```\n\nTo enter it, press P.\n\n## [Goto](/src/plugins/goto/README.md)\nThis plugin allows you to directly go to a certain step by either passing in a number or the ID of the step you'd like to go to.\n\n## [Progress](/src/plugins/progress/README.md)\nThis plugin, as its name implies, displays the progress in your presentation.\n\n## [Blackout](/src/plugins/blackout/blackout.js)\nThis plugin hides the screen when you press B, which is handy in many situations.\n\n## Other plugins\nYou may find the other plugins [here](/src/plugins/). It certainly helps if you familiarise yourself with the plugins.\n\n\n# Thank you for reading this\nIf you want to know more, you can always read the [DOCUMENTATION](DOCUMENTATION.md) or, even better, read the Source Code and try to understand how it works!\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2011-2016 Bartek Szopka\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 all\ncopies 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 THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "impress.js\n============\n\n[![CircleCI](https://circleci.com/gh/impress/impress.js.svg?style=svg)](https://circleci.com/gh/impress/impress.js)\n\nIt's a presentation framework based on the power of CSS3 transforms and\ntransitions in modern browsers and inspired by the idea behind prezi.com.\n\n**WARNING**\n\nimpress.js may not help you if you have nothing interesting to say ;)\n\n\nHOW TO USE IT\n---------------\n### Getting Started Guide\nCheck out our new [Getting Started](GettingStarted.md) guide if you want a quick introduction to the project!\n\n### Direct download link to only impress.js\nYou can include this link directly inside of your HTML file in its header. If you want to learn how to do this, you can find a how-to in the [Getting Started](GettingStarted.md) guide.\n- V2.0.0: https://cdn.jsdelivr.net/gh/impress/impress.js@2.0.0/js/impress.js\n- V1.1.0: https://cdn.jsdelivr.net/gh/impress/impress.js@1.1.0/js/impress.js\n- Source: https://cdn.jsdelivr.net/gh/impress/impress.js/js/impress.js\n\n### Getting Started Guide\nCheck out our new [Getting Started](GettingStarted.md) guide if you want a quick introduction to the project!\n\n### Checking out and initializing the git repository\n\n    git clone --recursive https://github.com/impress/impress.js.git\n    cd impress.js\n\nNote: For a minimal checkout, omit the `--recursive` option. This will leave out extra plugins.\n\n**Stable releases**\n\nNew features and fixes are continuously merged into the master branch, which is what the above command will check out. For the latest stable release, see the [Github Releases page](https://github.com/impress/impress.js/releases).\n\n\n### Documentation\n\n\nReference documentation of core impress.js features and API you can find it in [DOCUMENTATION.md](DOCUMENTATION.md).\n\nThe [HTML source code](index.html) of the official [impress.js demo](http://impress.github.io/impress.js/) serves as a good example usage and contains comments explaining various features of impress.js. For more information about styling you can look into [CSS code](css/impress-demo.css) which shows how classes provided by impress.js can be used. Last but not least [JavaScript code of impress.js](js/impress.js) has some useful comments if you are interested in how everything works. Feel free to explore!\n\n### Official demo\n\n[impress.js demo](http://impress.github.io/impress.js/) by [@bartaz](http://twitter.com/bartaz)\n\n### Examples and demos\n\nThe [Classic Slides](http://impress.github.io/impress.js/examples/classic-slides/) demo is targeted towards beginners, or can be used as a template for presentations that look like the traditional PowerPoint slide deck. Over time, it also grew into the example presentation that uses most of the features and addons available.\n\nMore examples and demos can be found on [Examples and demos wiki page](http://github.com/impress/impress.js/wiki/Examples-and-demos).\n\nFeel free to add your own example presentations (or websites) there.\n\n### Other tutorials and learning resources\n\nIf you want to learn even more there is a [list of tutorials and other learning resources](https://github.com/impress/impress.js/wiki/impress.js-tutorials-and-other-learning-resources)\non the wiki, too.\n\nThere is also a book available about [Building impressive presentations with impress.js](http://www.packtpub.com/building-impressive-presentations-with-impressjs/book) by Rakhitha Nimesh Ratnayake.\n\nYou may want to check out the sibling project [Impressionist](https://github.com/henrikingo/impressionist): a 3D GUI editor that can help you in creating impress.js presentations.\n\n### Mailing list\n\nYou're welcome to ask impress.js related questions on the [impressionist-presentations](https://groups.google.com/forum/#!forum/impressionist-presentations) mailing list.\n\n\nREPOSITORY STRUCTURE\n--------------------\n\n* [index.html](index.html): This is the official impress.js demo, showcasing all of the features of the original impress.js, as well as some new plugins as we add them.\n  * As already mentioned, this file is well commented and acts as the official tutorial.\n* [examples/](examples/): Contains several demos showcasing additional features available.\n  * [Classic Slides](examples/classic-slides/index.html) is a simple demo that you can use as template if you want to create very simple, rectangular, PowerPoint-like presentations.\n* [src/](src/): The main file is [src/impress.js](src/impress.js). Additional functionality is implemented as plugins in [src/plugins/](src/plugins/).\n  * See [src/plugins/README.md](src/plugins/README.md) for information about the plugin API and how to write plugins.\n* [test/](test/): Contains QUnit and Syn libraries that we use for writing tests, as well as some test coverage for core functionality. (Yes, more tests are much welcome.) Tests for plugins are in the directory of each plugin.\n* [js/](js/): Contains [js/impress.js](js/impress.js), which contains a concatenation of the core `src/impress.js` and all the plugins. Traditionally this is the file that you'll link to in a browser. In fact both the demo and test files do exactly that.\n* [css/](css/): Contains a CSS file used by the demo. This file is **not required for using impress.js** in your own presentations. Impress.js creates the CSS it needs dynamically.\n* [extras/](extras/) contains plugins that for various reasons aren't enabled by default. You have to explicitly add them with their own `script` element to use them.\n* [build.js](build.js): Simple build file that creates `js/impress.js`. It also creates a minified version `impress.min.js`, but that one is not included in the github repository.\n* [package.json](build.js): An NPM package specification. This was mainly added so you can easily install [buildify](https://www.npmjs.com/package/buildify) and run `node build.js`. Other than the build process, which is really just doing roughly `cat src/impress.js src/plugins/*/*.js > js/impress.js`, and testing, `impress.js` itself doesn't depend on Node or any NPM modules.\n\nWANT TO CONTRIBUTE?\n---------------------\n\nFor developers, once you've made changes to the code, you should run these commands for testing:\n\n    npm install\n    npm run all\n\nNote that running `firefox qunit_test_runner.html` is usually more informative than running `karma` with `npm run test`. They both run the same tests.\n\nMore info about the [src/](src/) directory can be found in [src/plugins/README.md](src/plugins/README.md).\n\n### Requirements\n\n* &gt;= node 7.6\n* npm\n\n\nABOUT THE NAME\n----------------\n\nimpress.js name is [courtesy of @skuzniak](http://twitter.com/skuzniak/status/143627215165333504).\n\nIt's an (un)fortunate coincidence that an Open/LibreOffice presentation tool is called Impress ;)\n\nReference API\n--------------\n\nSee the [Reference API](DOCUMENTATION.md)\n\nBROWSER SUPPORT\n-----------------\n\nThe design goal for impress.js has been to showcase awesome CSS3 features as found in modern browser versions. We also use some new DOM functionality, and specifically do not use jQuery or any other JavaScript libraries, nor our own functions, to support older browsers. In general, recent versions of Firefox and Chrome are known to work well. Reportedly IE now works too.\n\nThe typical use case for impress.js is to create presentations that you present from your own laptop, with a browser version you know works well. Some people also use impress.js successfully to embed animations or presentations in a web page, however, be aware that in this some of your visitors may not see the presentation correctly, or at all.\n\nIn particular, impress.js makes use of the following JS and CSS features:\n\n* [DataSet API](http://caniuse.com/#search=dataset)\n* [ClassList API](http://caniuse.com/#search=classlist)\n* [CSS 3D Transforms](http://caniuse.com/#search=css%203d)\n* [CSS Transitions](http://caniuse.com/#search=css%20transition)\n\nCOPYRIGHT AND LICENSE\n---------------------\n\nCopyright 2011-2012 Bartek Szopka (@bartaz), 2016-2023 Henrik Ingo (@henrikingo) and [70+ other contributors](https://github.com/impress/impress.js/graphs/contributors)\n\nReleased under the MIT [License](LICENSE)\n"
  },
  {
    "path": "build.js",
    "content": "const fs = require('fs');\nvar ls = require('ls');\nvar path = require('path');\nvar Terser = require(\"terser\");\n\nvar files = ['src/impress.js'];\n// Libraries from src/lib\nfiles.push('src/lib/gc.js', 'src/lib/util.js', 'src/lib/rotation.js')\n// Plugins from src/plugins\nfiles.push('src/plugins/autoplay/autoplay.js',\n           'src/plugins/blackout/blackout.js',\n           'src/plugins/extras/extras.js',\n           'src/plugins/form/form.js',\n           'src/plugins/fullscreen/fullscreen.js',\n           'src/plugins/goto/goto.js',\n           'src/plugins/bookmark/bookmark.js',\n           'src/plugins/help/help.js',\n           'src/plugins/impressConsole/impressConsole.js',\n           'src/plugins/media/media.js',\n           'src/plugins/mobile/mobile.js',\n           'src/plugins/mouse-timeout/mouse-timeout.js',\n           'src/plugins/navigation/navigation.js',\n           'src/plugins/navigation-ui/navigation-ui.js',\n           'src/plugins/progress/progress.js',\n           'src/plugins/rel/rel.js',\n           'src/plugins/resize/resize.js',\n           'src/plugins/skip/skip.js',\n           'src/plugins/stop/stop.js',\n           'src/plugins/substep/substep.js',\n           'src/plugins/touch/touch.js',\n           'src/plugins/toolbar/toolbar.js')\nvar output = files.map((f)=>{\n  return fs.readFileSync(f).toString();\n}).join('\\n')\n\nvar filename = 'js/impress.js';\nfs.writeFileSync(filename, '// This file was automatically generated from files in src/ directory.\\n\\n' + output)\nconsole.log(filename);\n\n// terser --compress --mangle --comments '/^!/' --source-map --output js/impress.min.js js/impress.js\nvar code = fs.readFileSync('js/impress.js').toString();\nvar options = {\n  sourceMap: {\n   filename: 'js/impress.js',\n   url: 'js/impress.min.js.map'\n  },\n  output: {\n    comments: /^!/\n  }\n};\nvar result = Terser.minify({'js/impress.js': code}, options);\n\nfilename = 'js/impress.min.js';\nfs.writeFileSync(filename, result.code);\nconsole.log(filename);\nfilename = 'js/impress.min.js.map';\nfs.writeFileSync(filename, result.map);\nconsole.log(filename);\n\n/* Auto generate an index.html that lists all the directories under examples/\n * This is useful for gh-pages, so you can link to http://impress.github.io/impress.js/examples\n */\nvar html_list = '<ul><br />\\n'\nls( 'examples/*', { type: 'dir' }).forEach(function(dir) {\n    html_list += '  <li><a href=\"' + dir['file'] + '/\">' + dir['name'] + '</a></li>\\n';\n});\nhtml_list += '</ul>\\n'\n\nvar html = '<html>\\n<head>\\n<title>Example presentations</title>\\n</head>\\n<body>'\nhtml += '<h1>Example presentations</h1>\\n' + html_list\nhtml += '</body>\\n</html>'\n\nfilename = path.resolve(__dirname, 'examples', 'index.html');\nfs.writeFileSync(filename, html);\nconsole.log(filename);\n"
  },
  {
    "path": "css/impress-common.css",
    "content": "/* impress.js doesn't require any particular CSS file. \nEach author should create their own, to achieve the visual style they want. \nYet in practice many plugins will not do anything useful without CSS. (See for example mouse-timeout plugin.) \nThis file contains sample CSS that you may want to use in your presentation. \nIt is focused on plugin functionality, not the visual style of your presentation. */\n\n/* Using the substep plugin, hide bullet points at first, then show them one by one. */\n#impress .step .substep {\n    opacity: 0;\n}\n\n#impress .step .substep.substep-visible {\n    opacity: 1;\n    transition: opacity 1s;\n}\n/*\n  Speaker notes allow you to write comments within the steps, that will not \n  be displayed as part of the presentation. However, they will be picked up\n  and displayed by impressConsole.js when you press P.\n*/\n.notes {\n    display: none;\n}\n\n/* Toolbar plugin */\n.impress-enabled div#impress-toolbar {\n    position: fixed;\n    right: 1px;\n    bottom: 1px;\n    opacity: 0.6;\n    z-index: 10;\n}\n.impress-enabled div#impress-toolbar > span {\n    margin-right: 10px;\n}\n.impress-enabled div#impress-toolbar.impress-toolbar-show {\n    display: block;\n}\n.impress-enabled div#impress-toolbar.impress-toolbar-hide {\n    display: none;\n}\n\n/* Progress bar */\n.impress-progress {\n    position: absolute;\n    left: 59px;\n    bottom: 1px;\n    text-align: left;\n    font-size: 10pt;\n    opacity: 0.6;\n}\n.impress-enabled .impress-progressbar {\n  position: absolute;\n  right: 318px;\n  bottom: 1px;\n  left: 118px;\n  border-radius: 7px;\n  border: 2px solid rgba(100, 100, 100, 0.2);\n}\n.impress-progressbar {\n    right: 118px;\n}\n.impress-progressbar DIV {\n    width: 0;\n    height: 2px;\n    border-radius: 5px;\n    background: rgba(75, 75, 75, 0.4);\n    transition: width 1s linear;\n}\n.impress-enabled .impress-progress {\n  position: absolute;\n  left: 59px;\n  bottom: 1px;\n  text-align: left;\n  opacity: 0.6;\n}\n.impress-enabled #impress-help {\n    background: none repeat scroll 0 0 rgba(0, 0, 0, 0.5);\n    color: #EEEEEE;\n    font-size: 80%;\n    position: fixed;\n    left: 2em;\n    bottom: 2em;\n    width: 24em;\n    border-radius: 1em;\n    padding: 1em;\n    text-align: center;\n    z-index: 100;\n    font-family: Verdana, Arial, Sans;\n}\n.impress-enabled #impress-help td {\n    padding-left: 1em;\n    padding-right: 1em;\n}\n\n/*\n  We might want to hide the help, toolbar, progress and progress bar in the\n  preView window of the impressConsole that is displayed when you press P.\n*/\n.impress-console.preView .impress-progress,\n.impress-console.preView .impress-progressbar,\n.impress-console.preView #impress-toolbar,\n.impress-console.preView #impress-help {\n  display: none;\n}\n/*\n  Hide the help in the slideView as well.\n*/\n.impress-console.slideView #impress-help {\n  display: none;\n}\n\n/*\n    With help from the mouse-timeout plugin, we can hide the toolbar and\n    have it show only when you move/click/touch the mouse.\n*/\nbody.impress-mouse-timeout div#impress-toolbar {\n    display: none;\n}\n\n/*\n    In fact, we can hide the mouse cursor itself too, when mouse isn't used.\n*/\nbody.impress-mouse-timeout {\n    cursor: none;\n}\n\n\n/*\n    And as the last thing there is a workaround for quite strange bug.\n    It happens a lot in Chrome. I don't remember if I've seen it in Firefox.\n\n    Sometimes the element positioned in 3D (especially when it's moved back\n    along Z axis) is not clickable, because it falls 'behind' the <body>\n    element.\n\n    To prevent this, I decided to make <body> non clickable by setting\n    pointer-events property to `none` value.\n    Value if this property is inherited, so to make everything else clickable\n    I bring it back on the #impress element.\n\n    If you want to know more about `pointer-events` here are some docs:\n    https://developer.mozilla.org/en/CSS/pointer-events\n\n    There is one very important thing to notice about this workaround - it makes\n    everything 'unclickable' except what's in #impress element.\n\n    So use it wisely ... or don't use at all.\n*/\n\n.impress-enabled                          { pointer-events: none }\n.impress-enabled #impress                 { pointer-events: auto }\n\n /*If you disable pointer-events, you need to re-enable them for the toolbar.\n   And the speaker console while at it.*/\n\n.impress-enabled #impress-toolbar         { pointer-events: auto }\n.impress-enabled #impress-console-button  { pointer-events: auto }\n\n\n/*\n    There is one funny thing I just realized.\n\n    Thanks to this workaround above everything except #impress element is invisible\n    for click events. That means that the hint element is also not clickable.\n    So basically all of this transforms and delayed transitions trickery was probably\n    not needed at all...\n\n    But it was fun to learn about it, wasn't it?\n*/\n\n\n"
  },
  {
    "path": "css/impress-demo.css",
    "content": "/*\n    So you like the style of impress.js demo?\n    Or maybe you are just curious how it was done?\n\n    You couldn't find a better place to find out!\n\n    Welcome to the stylesheet impress.js demo presentation.\n\n    Please remember that it is not meant to be a part of impress.js and is\n    not required by impress.js.\n    I expect that anyone creating a presentation for impress.js would create\n    their own set of styles.\n\n    But feel free to read through it and learn how to get the most of what\n    impress.js provides.\n\n    And let me be your guide.\n\n    Shall we begin?\n*/\n\n\n/*\n    We start with a good ol' reset.\n    That's the one by Eric Meyer http://meyerweb.com/eric/tools/css/reset/\n\n    You can probably argue if it is needed here, or not, but for sure it\n    doesn't do any harm and gives us a fresh start.\n*/\n\nhtml, body, div, span, applet, object, iframe,\nh1, h2, h3, h4, h5, h6, p, blockquote, pre,\na, abbr, acronym, address, big, cite, code,\ndel, dfn, em, img, ins, kbd, q, s, samp,\nsmall, strike, strong, sub, sup, tt, var,\nb, u, i, center,\ndl, dt, dd, ol, ul, li,\nfieldset, form, label, legend,\ntable, caption, tbody, tfoot, thead, tr, th, td,\narticle, aside, canvas, details, embed,\nfigure, figcaption, footer, header, hgroup,\nmenu, nav, output, ruby, section, summary,\ntime, mark, audio, video {\n    margin: 0;\n    padding: 0;\n    border: 0;\n    font-size: 100%;\n    font: inherit;\n    vertical-align: baseline;\n}\n\n/* HTML5 display-role reset for older browsers */\narticle, aside, details, figcaption, figure,\nfooter, header, hgroup, menu, nav, section {\n    display: block;\n}\nbody {\n    line-height: 1;\n}\nol, ul {\n    list-style: none;\n}\nblockquote, q {\n    quotes: none;\n}\nblockquote:before, blockquote:after,\nq:before, q:after {\n    content: '';\n    content: none;\n}\n\ntable {\n    border-collapse: collapse;\n    border-spacing: 0;\n}\n\n/*\n    Now here is when interesting things start to appear.\n\n    We set up <body> styles with default font and nice gradient in the background.\n    And yes, there is a lot of repetition there because of -prefixes but we don't\n    want to leave anybody behind.\n*/\nbody {\n    font-family: 'PT Sans', sans-serif;\n    min-height: 740px;\n\n    background: rgb(215, 215, 215);\n    background: -webkit-gradient(radial, 50% 50%, 0, 50% 50%, 500, from(rgb(240, 240, 240)), to(rgb(190, 190, 190)));\n    background: -webkit-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190));\n    background:    -moz-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190));\n    background:     -ms-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190));\n    background:      -o-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190));\n    background:         radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190));\n}\n\n/*\n    Now let's bring some text styles back ...\n*/\nb, strong { font-weight: bold }\ni, em { font-style: italic }\n\n/*\n    ... and give links a nice look.\n*/\na {\n    color: inherit;\n    text-decoration: none;\n    padding: 0 0.1em;\n    background: rgba(255,255,255,0.5);\n    text-shadow: -1px -1px 2px rgba(100,100,100,0.9);\n    border-radius: 0.2em;\n\n    -webkit-transition: 0.5s;\n    -moz-transition:    0.5s;\n    -ms-transition:     0.5s;\n    -o-transition:      0.5s;\n    transition:         0.5s;\n}\n\na:hover,\na:focus {\n    background: rgba(255,255,255,1);\n    text-shadow: -1px -1px 2px rgba(100,100,100,0.5);\n}\n\n/*\n    Because the main point behind the impress.js demo is to demo impress.js\n    we display a fallback message for users with browsers that don't support\n    all the features required by it.\n\n    All of the content will be still fully accessible for them, but I want\n    them to know that they are missing something - that's what the demo is\n    about, isn't it?\n\n    And then we hide the message, when support is detected in the browser.\n*/\n\n.fallback-message {\n    font-family: sans-serif;\n    line-height: 1.3;\n\n    width: 780px;\n    padding: 10px 10px 0;\n    margin: 20px auto;\n\n    border: 1px solid #E4C652;\n    border-radius: 10px;\n    background: #EEDC94;\n}\n\n.fallback-message p {\n    margin-bottom: 10px;\n}\n\n.impress-supported .fallback-message {\n    display: none;\n}\n\n/*\n    Now let's style the presentation steps.\n\n    We start with basics to make sure it displays correctly in everywhere ...\n*/\n\n.step {\n    position: relative;\n    width: 900px;\n    padding: 40px;\n    margin: 20px auto;\n\n    -webkit-box-sizing: border-box;\n    -moz-box-sizing:    border-box;\n    -ms-box-sizing:     border-box;\n    -o-box-sizing:      border-box;\n    box-sizing:         border-box;\n\n    font-family: 'PT Serif', georgia, serif;\n    font-size: 48px;\n    line-height: 1.5;\n}\n\n/*\n    ... and we enhance the styles for impress.js.\n\n    Basically we remove the margin and make inactive steps a little bit transparent.\n*/\n.impress-enabled .step {\n    margin: 0;\n    opacity: 0.3;\n\n    -webkit-transition: opacity 1s;\n    -moz-transition:    opacity 1s;\n    -ms-transition:     opacity 1s;\n    -o-transition:      opacity 1s;\n    transition:         opacity 1s;\n}\n\n.impress-enabled .step.active { opacity: 1 }\n\n/*\n    These 'slide' step styles were heavily inspired by HTML5 Slides:\n    http://html5slides.googlecode.com/svn/trunk/styles.css\n\n    ;)\n\n    They cover everything what you see on first three steps of the demo.\n\n    All impress.js steps are wrapped inside a div element of 0 size! This means that relative\n    values for width and height (example: `width: 100%`) will not work. You need to use pixel\n    values. The pixel values used here correspond to the data-width and data-height given to the\n    #impress root element. When the presentation is viewed on a larger or smaller screen, impress.js\n    will automatically scale the steps to fit the screen.\n*/\n.slide {\n    display: block;\n\n    width: 900px;\n    height: 700px;\n    padding: 40px 60px;\n\n    background-color: white;\n    border: 1px solid rgba(0, 0, 0, .3);\n    border-radius: 10px;\n    box-shadow: 0 2px 6px rgba(0, 0, 0, .1);\n\n    color: rgb(102, 102, 102);\n    text-shadow: 0 2px 2px rgba(0, 0, 0, .1);\n\n    font-family: 'Open Sans', Arial, sans-serif;\n    font-size: 30px;\n    line-height: 36px;\n    letter-spacing: -1px;\n}\n\n.slide q {\n    display: block;\n    font-size: 50px;\n    line-height: 72px;\n\n    margin-top: 100px;\n}\n\n.slide q strong {\n    white-space: nowrap;\n}\n\n/*\n    And now we start to style each step separately.\n\n    I agree that this may be not the most efficient, object-oriented and\n    scalable way of styling, but most of steps have quite a custom look\n    and typography tricks here and there, so they had to be styled separately.\n\n    First is the title step with a big <h1> (no room for padding) and some\n    3D positioning along Z axis.\n*/\n\n#title {\n    padding: 0;\n}\n\n#title .try {\n    font-size: 64px;\n    position: absolute;\n    top: -0.5em;\n    left: 1.5em;\n\n    -webkit-transform: translateZ(20px);\n    -moz-transform:    translateZ(20px);\n    -ms-transform:     translateZ(20px);\n    -o-transform:      translateZ(20px);\n    transform:         translateZ(20px);\n}\n\n#title h1 {\n    font-size: 180px;\n\n    -webkit-transform: translateZ(50px);\n    -moz-transform:    translateZ(50px);\n    -ms-transform:     translateZ(50px);\n    -o-transform:      translateZ(50px);\n    transform:         translateZ(50px);\n}\n\n#title .footnote {\n    font-size: 32px;\n}\n\n/*\n    Second step is nothing special, just a text with a link, so it doesn't need\n    any special styling.\n\n    Let's move to 'big thoughts' with centered text and custom font sizes.\n*/\n#big {\n    width: 600px;\n    text-align: center;\n    font-size: 60px;\n    line-height: 1;\n}\n\n#big strong,\n#big b {\n    display: block;\n    font-size: 250px;\n    line-height: 250px;\n}\n\n#big .thoughts {\n    font-size: 90px;\n    line-height: 150px;\n}\n\n/*\n    'Tiny ideas' just need some tiny styling.\n*/\n#tiny {\n    width: 500px;\n    text-align: center;\n}\n\n/*\n    This step has some animated text ...\n*/\n#ing { width: 500px }\n\n/*\n    ... so we define display to `inline-block` to enable transforms and\n    transition duration to 0.5s ...\n*/\n#ing b {\n    display: inline-block;\n    -webkit-transition: 0.5s;\n    -moz-transition:    0.5s;\n    -ms-transition:     0.5s;\n    -o-transition:      0.5s;\n    transition:         0.5s;\n}\n\n/*\n    ... and we want 'positioning` word to move up a bit when the step gets\n    `present` class ...\n*/\n#ing.present .positioning {\n    -webkit-transform: translateY(-10px);\n    -moz-transform:    translateY(-10px);\n    -ms-transform:     translateY(-10px);\n    -o-transform:      translateY(-10px);\n    transform:         translateY(-10px);\n}\n\n/*\n    ... 'rotating' to rotate a quarter of a second later ...\n*/\n#ing.present .rotating {\n    -webkit-transform: rotate(-10deg);\n    -moz-transform:    rotate(-10deg);\n    -ms-transform:     rotate(-10deg);\n    -o-transform:      rotate(-10deg);\n    transform:         rotate(-10deg);\n\n    -webkit-transition-delay: 0.25s;\n    -moz-transition-delay:    0.25s;\n    -ms-transition-delay:     0.25s;\n    -o-transition-delay:      0.25s;\n    transition-delay:         0.25s;\n}\n\n/*\n    ... and 'scaling' to scale down after another quarter of a second.\n*/\n#ing.present .scaling {\n    -webkit-transform: scale(0.7);\n    -moz-transform:    scale(0.7);\n    -ms-transform:     scale(0.7);\n    -o-transform:      scale(0.7);\n    transform:         scale(0.7);\n\n    -webkit-transition-delay: 0.5s;\n    -moz-transition-delay:    0.5s;\n    -ms-transition-delay:     0.5s;\n    -o-transition-delay:      0.5s;\n    transition-delay:         0.5s;\n}\n\n/*\n    The 'imagination' step is again some boring font-sizing.\n*/\n\n#imagination {\n    width: 600px;\n}\n\n#imagination .imagination {\n    font-size: 78px;\n}\n\n/*\n    There is nothing really special about 'use the source, Luke' step, too,\n    except maybe of the Yoda background.\n\n    As you can see below I've 'hard-coded' it in data URL.\n    That's not the best way to serve images, but because that's just this one\n    I decided it will be OK to have it this way.\n\n    Just make sure you don't blindly copy this approach.\n*/\n#source {\n    width: 700px;\n    padding-bottom: 300px;\n\n    /* Yoda Icon :: Pixel Art from Star Wars http://www.pixeljoint.com/pixelart/1423.htm */\n    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARgAAAEYCAMAAACwUBm+AAAAAXNSR0IArs4c6QAAAKtQTFRFsAAAvbWSLUUrLEQqY1s8UYJMqJ1vNTEgOiIdIzYhjIFVLhsXZ6lgSEIsP2U8JhcCVzMsSXZEgXdOO145XJdWOl03LzAYMk4vSXNExr+hwcuxRTs1Qmk+RW9Am49eFRANQz4pUoNMQWc+OSMDTz0wLBsCNVMxa2NBOyUDUoNNSnlEWo9VRGxAVzYFl6tXCggHbLNmMUIcHhwTXkk5f3VNRT8wUT8xAAAACQocRBWFFwAAAAF0Uk5TAEDm2GYAAAPCSURBVHja7d3JctNAFIZRMwRCCGEmzPM8z/D+T8bu/ptbXXJFdij5fMt2Wuo+2UgqxVmtttq5WVotLzBgwIABAwYMGDCn0qVqbo69psPqVpWx+1XG5iaavF8wYMCAAQMGDBgwi4DJ6Y6qkxB1HNlcN3a92gbR5P2CAQMGDBgwYMCAWSxMlrU+UY5yu2l9okfV4bAxUVbf7TJnAwMGDBgwYMCAAbMLMHeqbGR82Zy+VR1Ht81nVca6R+UdTLaU24Ruzd3qM/e4yjnAgAEDBgwYMGDA7AJMd1l/3NRdVGcj3eX/2WEhCmDGxnM7yqygu8XIPjJj8iN/MGDAgAEDBgwYMAuDGb8q0RGlLCHLv1t9qDKWn3vdNHVuEI6HPaxO9Jo3GDBgwIABAwYMmIXBdC9ShGgMk+XnkXUeuGcsP/e1+lhNnZsL/G5Vs3OAAQMGDBgwYMCAWSxMR3SzOmraG5atdy9wZKzb+vg16qyqe2FltbnAgAEDBgwYMGDALAxmTJSuN3WA76rnVca6GTnemGN1WoEBAwYMGDBgwIBZGMxUomy4+xO899V4LAg5Xnc2MGDAgAEDBgwYMGA218Wq+2K1LDqvY9xZu8zN8fICdM6btYABAwYMGDBgwIABMzfH0+pGU5afze2tXebmeAfVz+p8BQYMGDBgwIABAwbMPBzZ+oWmfJrln1273FhkbHzee9WWbw7AgAEDBgwYMGDALAKm43hcdctKgblcPamOhuXnXlY5Xs6bsW4FGyQCAwYMGDBgwIABswiYMceZKgvMo+h8mrHLTdn676rj+FEFoTtHd8MwOxEYMGDAgAEDBgyYRcBM5UhXqiymW3R3c9ARhWO/OmjqfjVZy+xEYMCAAQMGDBgwYBYG073OnCV0RFNhMhaOa9WfKmOB6XjHMN1tQmaAAQMGDBgwYMCA2VWY7vXjz1U4croAzgPztwIDBgwYMGDAgAEDZhswh035NBw59Dww3RgYMGDAgAEDBgwYMJuD6f4tXT7NUqfCdBvZLkxXdgQGDBgwYMCAAQNmt2DGj8WzwAfV/w7T/aq7mxwwYMCAAQMGDBgwuwqTOo7uTwTngflSzQ3TdaJvAwEDBgwYMGDAgAED5gSvgbyo5oHZ4Pc+gwEDBgwYMGDAgAEzhOm+5G0qTGaAAQMGDBgwYMCAAXNaMOcnls3tNwWm+zRzp54NDBgwYMCAAQMGDJh5YNL36k1TLuGvVq+qnKMbS5n7tulT9asCAwYMGDBgwIABA2ZumKuztLnjgQEDBgwYMGDAgNl5mH/4/ltKA6vBNAAAAABJRU5ErkJggg==);\n    background-position: bottom right;\n    background-repeat: no-repeat;\n}\n\n#source q {\n    font-size: 60px;\n}\n\n/*\n    And the \"it's in 3D\" step again brings some 3D typography - just for fun.\n\n    Because we want to position <span> elements in 3D we set transform-style to\n    `preserve-3d` on the paragraph.\n    It is not needed by webkit browsers, but it is in Firefox. It's hard to say\n    which behaviour is correct as 3D transforms spec is not very clear about it.\n*/\n#its-in-3d p {\n    -webkit-transform-style: preserve-3d;\n    -moz-transform-style:    preserve-3d; /* Y U need this Firefox?! */\n    -ms-transform-style:     preserve-3d;\n    -o-transform-style:      preserve-3d;\n    transform-style:         preserve-3d;\n}\n\n/*\n    Below we position each word separately along Z axis and we want it to transition\n    to default position in 0.5s when the step gets `present` class.\n\n    Quite a simple idea, but lot's of styles and prefixes.\n*/\n#its-in-3d span,\n#its-in-3d b {\n    display: inline-block;\n    -webkit-transform: translateZ(40px);\n    -moz-transform:    translateZ(40px);\n    -ms-transform:     translateZ(40px);\n    -o-transform:      translateZ(40px);\n     transform:        translateZ(40px);\n\n    -webkit-transition: 0.5s;\n    -moz-transition:    0.5s;\n    -ms-transition:     0.5s;\n    -o-transition:      0.5s;\n    transition:         0.5s;\n}\n\n#its-in-3d .have {\n    -webkit-transform: translateZ(-40px);\n    -moz-transform:    translateZ(-40px);\n    -ms-transform:     translateZ(-40px);\n    -o-transform:      translateZ(-40px);\n    transform:         translateZ(-40px);\n}\n\n#its-in-3d .you {\n    -webkit-transform: translateZ(20px);\n    -moz-transform:    translateZ(20px);\n    -ms-transform:     translateZ(20px);\n    -o-transform:      translateZ(20px);\n    transform:         translateZ(20px);\n}\n\n#its-in-3d .noticed {\n    -webkit-transform: translateZ(-40px);\n    -moz-transform:    translateZ(-40px);\n    -ms-transform:     translateZ(-40px);\n    -o-transform:      translateZ(-40px);\n    transform:         translateZ(-40px);\n}\n\n#its-in-3d .its {\n    -webkit-transform: translateZ(60px);\n    -moz-transform:    translateZ(60px);\n    -ms-transform:     translateZ(60px);\n    -o-transform:      translateZ(60px);\n    transform:         translateZ(60px);\n}\n\n#its-in-3d .in {\n    -webkit-transform: translateZ(-10px);\n    -moz-transform:    translateZ(-10px);\n    -ms-transform:     translateZ(-10px);\n    -o-transform:      translateZ(-10px);\n    transform:         translateZ(-10px);\n}\n\n#its-in-3d .footnote {\n    font-size: 32px;\n\n    -webkit-transform: translateZ(-10px);\n    -moz-transform:    translateZ(-10px);\n    -ms-transform:     translateZ(-10px);\n    -o-transform:      translateZ(-10px);\n    transform:         translateZ(-10px);\n}\n\n#its-in-3d.present span,\n#its-in-3d.present b {\n    -webkit-transform: translateZ(0px);\n    -moz-transform:    translateZ(0px);\n    -ms-transform:     translateZ(0px);\n    -o-transform:      translateZ(0px);\n    transform:         translateZ(0px);\n}\n\n/*\n    The last step is an overview.\n    There is no content in it, so we make sure it's not visible because we want\n    to be able to click on other steps.\n\n*/\n#overview { display: none }\n\n/*\n    We also make other steps visible and give them a pointer cursor using the\n    `impress-on-` class.\n*/\n.impress-on-overview .step {\n    opacity: 1;\n    cursor: pointer;\n}\n\n/*\n    Now, when we have all the steps styled let's give users a hint how to navigate\n    around the presentation.\n\n    The best way to do this would be to use JavaScript, show a delayed hint for a\n    first time users, then hide it and store a status in cookie or localStorage...\n\n    But I wanted to have some CSS fun and avoid additional scripting...\n\n    Let me explain it first, so maybe the transition magic will be more readable\n    when you read the code.\n\n    First of all I wanted the hint to appear only when user is idle for a while.\n    You can't detect the 'idle' state in CSS, but I delayed a appearing of the\n    hint by 5s using transition-delay.\n\n    You also can't detect in CSS if the user is a first-time visitor, so I had to\n    make an assumption that I'll only show the hint on the first step. And when\n    the step is changed hide the hint, because I can assume that user already\n    knows how to navigate.\n\n    To summarize it - hint is shown when the user is on the first step for longer\n    than 5 seconds.\n\n    The other problem I had was caused by the fact that I wanted the hint to fade\n    in and out. It can be easily achieved by transitioning the opacity property.\n    But that also meant that the hint was always on the screen, even if totally\n    transparent. It covered part of the screen and you couldn't correctly clicked\n    through it.\n    Unfortunately you cannot transition between display `block` and `none` in pure\n    CSS, so I needed a way to not only fade out the hint but also move it out of\n    the screen.\n\n    I solved this problem by positioning the hint below the bottom of the screen\n    with CSS transform and moving it up to show it. But I also didn't want this move\n    to be visible. I wanted the hint only to fade in and out visually, so I delayed\n    the fade in transition, so it starts when the hint is already in its correct\n    position on the screen.\n\n    I know, it sounds complicated ... maybe it would be easier with the code?\n*/\n\n.hint {\n    /*\n        We hide the hint until presentation is started and from browsers not supporting\n        impress.js, as they will have a linear scrollable view ...\n    */\n    display: none;\n\n    /*\n        ... and give it some fixed position and nice styles.\n    */\n    position: fixed;\n    left: 0;\n    right: 0;\n    bottom: 200px;\n\n    background: rgba(0,0,0,0.5);\n    color: #EEE;\n    text-align: center;\n\n    font-size: 50px;\n    padding: 20px;\n\n    z-index: 100;\n\n    /*\n        By default we don't want the hint to be visible, so we make it transparent ...\n    */\n    opacity: 0;\n\n    /*\n        ... and position it below the bottom of the screen (relative to it's fixed position)\n    */\n    -webkit-transform: translateY(400px);\n    -moz-transform:    translateY(400px);\n    -ms-transform:     translateY(400px);\n    -o-transform:      translateY(400px);\n    transform:         translateY(400px);\n\n    /*\n        Now let's imagine that the hint is visible and we want to fade it out and move out\n        of the screen.\n\n        So we define the transition on the opacity property with 1s duration and another\n        transition on transform property delayed by 1s so it will happen after the fade out\n        on opacity finished.\n\n        This way user will not see the hint moving down.\n    */\n    -webkit-transition: opacity 1s, -webkit-transform 0.5s 1s;\n    -moz-transition:    opacity 1s,    -moz-transform 0.5s 1s;\n    -ms-transition:     opacity 1s,     -ms-transform 0.5s 1s;\n    -o-transition:      opacity 1s,      -o-transform 0.5s 1s;\n    transition:         opacity 1s,         transform 0.5s 1s;\n}\n\n/*\n    Now we 'enable' the hint when presentation is initialized ...\n*/\n.impress-enabled .hint { display: block }\n\n/*\n    ... and we will show it when the first step (with id 'bored') is active.\n*/\n.impress-on-bored .hint {\n    /*\n        We remove the transparency and position the hint in its default fixed\n        position.\n    */\n    opacity: 1;\n\n    -webkit-transform: translateY(0px);\n    -moz-transform:    translateY(0px);\n    -ms-transform:     translateY(0px);\n    -o-transform:      translateY(0px);\n    transform:         translateY(0px);\n\n    /*\n        Now for fade in transition we have the oposite situation from the one\n        above.\n\n        First after 4.5s delay we animate the transform property to move the hint\n        into its correct position and after that we fade it in with opacity\n        transition.\n    */\n    -webkit-transition: opacity 1s 5s, -webkit-transform 0.5s 4.5s;\n    -moz-transition:    opacity 1s 5s,    -moz-transform 0.5s 4.5s;\n    -ms-transition:     opacity 1s 5s,     -ms-transform 0.5s 4.5s;\n    -o-transition:      opacity 1s 5s,      -o-transform 0.5s 4.5s;\n    transition:         opacity 1s 5s,         transform 0.5s 4.5s;\n}\n\n/*\n    That's all I have for you in this file.\n    Thanks for reading. I hope you enjoyed it at least as much as I enjoyed writing it\n    for you.\n*/\n"
  },
  {
    "path": "examples/2D-navigation/css/fonts.css",
    "content": "/* latin-ext */\n@font-face {\n  font-family: 'Cutive Mono';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Cutive Mono'), local('CutiveMono-Regular'), url(http://fonts.gstatic.com/s/cutivemono/v4/N5odNRruTwjvCM8y77PhQSYE0-AqJ3nfInTTiDXDjU4.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'Cutive Mono';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Cutive Mono'), local('CutiveMono-Regular'), url(http://fonts.gstatic.com/s/cutivemono/v4/N5odNRruTwjvCM8y77PhQY4P5ICox8Kq3LLUNMylGO4.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/K88pR3goAWT7BTt32Z01mxJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/RjgO7rYTmqiVp7vzi-Q5URJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/LWCjsQkB6EMdfHrEVqA1KRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/xozscpT2726on7jbcb_pAhJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0370-03FF;\n}\n/* vietnamese */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/59ZRklaO5bWGqF5A9baEERJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/u-WUoqrET9fUeobQW7jkRRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/cJZKeOuBrn4kERxqtaUH3VtXRa8TVwTICgirnJhmVJw.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSq-j2U0lmluP9RWlSytm3ho.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSpX5f-9o1vgP2EXwfjgl7AY.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNShWV49_lSm1NYrwo-zkhivY.woff2) format('woff2');\n  unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSqaRobkAwv3vxw3jMhVENGA.woff2) format('woff2');\n  unicode-range: U+0370-03FF;\n}\n/* vietnamese */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSv8zf_FOSsgRmwsS7Aa9k2w.woff2) format('woff2');\n  unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSj0LW-43aMEzIO6XUTLjad8.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSugdm0LZdjqr5-oayXSOefg.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBjTOQ_MqJVwkKsUn0wKzc2I.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBjUj_cnvWIuuBMVgbX098Mw.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBkbcKLIaa1LC45dFaAfauRA.woff2) format('woff2');\n  unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBmo_sUJ8uO4YLWRInS22T3Y.woff2) format('woff2');\n  unicode-range: U+0370-03FF;\n}\n/* vietnamese */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBr6up8jxqWt8HVA3mDhkV_0.woff2) format('woff2');\n  unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBiYE0-AqJ3nfInTTiDXDjU4.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBo4P5ICox8Kq3LLUNMylGO4.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxmgpAmOCqD37_tyH_8Ri5MM.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxsPNMTLbnS9uQzHQlYieHUU.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxgyhumQnPMBCoGYhRaNxyyY.woff2) format('woff2');\n  unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxhUVAXEdVvYDDqrz3aeR0Yc.woff2) format('woff2');\n  unicode-range: U+0370-03FF;\n}\n/* vietnamese */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxlf4y_3s5bcYyyLIFUSWYUU.woff2) format('woff2');\n  unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxnywqdtBbUHn3VPgzuFrCy8.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxl2umOyRU7PgRiv8DXcgJjk.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Sans'), local('PTSans-Regular'), url(http://fonts.gstatic.com/s/ptsans/v8/JX7MlXqjSJNjQvI4heMMGvY6323mHUZFJMgTvxaG2iE.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Sans'), local('PTSans-Regular'), url(http://fonts.gstatic.com/s/ptsans/v8/vtwNVMP8y9C17vLvIBNZI_Y6323mHUZFJMgTvxaG2iE.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Sans'), local('PTSans-Regular'), url(http://fonts.gstatic.com/s/ptsans/v8/9kaD4V2pNPMMeUVBHayd7vY6323mHUZFJMgTvxaG2iE.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Sans'), local('PTSans-Regular'), url(http://fonts.gstatic.com/s/ptsans/v8/ATKpv8nLYAKUYexo8iqqrg.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Sans Bold'), local('PTSans-Bold'), url(http://fonts.gstatic.com/s/ptsans/v8/kTYfCWJhlldPf5LnG4ZnHCEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Sans Bold'), local('PTSans-Bold'), url(http://fonts.gstatic.com/s/ptsans/v8/g46X4VH_KHOWAAa-HpnGPiEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Sans Bold'), local('PTSans-Bold'), url(http://fonts.gstatic.com/s/ptsans/v8/hpORcvLZtemlH8gI-1S-7iEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Sans Bold'), local('PTSans-Bold'), url(http://fonts.gstatic.com/s/ptsans/v8/0XxGQsSc1g4rdRdjJKZrNPk_vArhqVIZ0nv9q090hN8.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Sans Italic'), local('PTSans-Italic'), url(http://fonts.gstatic.com/s/ptsans/v8/GpWpM_6S4VQLPNAQ3iWvVRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Sans Italic'), local('PTSans-Italic'), url(http://fonts.gstatic.com/s/ptsans/v8/7dSh6BcuqDLzS2qAASIeuhJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Sans Italic'), local('PTSans-Italic'), url(http://fonts.gstatic.com/s/ptsans/v8/DVKQJxMmC9WF_oplMzlQqRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Sans Italic'), local('PTSans-Italic'), url(http://fonts.gstatic.com/s/ptsans/v8/PIPMHY90P7jtyjpXuZ2cLFtXRa8TVwTICgirnJhmVJw.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Sans Bold Italic'), local('PTSans-BoldItalic'), url(http://fonts.gstatic.com/s/ptsans/v8/lILlYDvubYemzYzN7GbLkK-j2U0lmluP9RWlSytm3ho.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Sans Bold Italic'), local('PTSans-BoldItalic'), url(http://fonts.gstatic.com/s/ptsans/v8/lILlYDvubYemzYzN7GbLkJX5f-9o1vgP2EXwfjgl7AY.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Sans Bold Italic'), local('PTSans-BoldItalic'), url(http://fonts.gstatic.com/s/ptsans/v8/lILlYDvubYemzYzN7GbLkD0LW-43aMEzIO6XUTLjad8.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Sans Bold Italic'), local('PTSans-BoldItalic'), url(http://fonts.gstatic.com/s/ptsans/v8/lILlYDvubYemzYzN7GbLkOgdm0LZdjqr5-oayXSOefg.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Serif'), local('PTSerif-Regular'), url(http://fonts.gstatic.com/s/ptserif/v8/5hX15RUpPERmeybVlLQEWBTbgVql8nDJpwnrE27mub0.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Serif'), local('PTSerif-Regular'), url(http://fonts.gstatic.com/s/ptserif/v8/fU0HAfLiPHGlZhZpY6M7dBTbgVql8nDJpwnrE27mub0.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Serif'), local('PTSerif-Regular'), url(http://fonts.gstatic.com/s/ptserif/v8/CPRt--GVMETgA6YEaoGitxTbgVql8nDJpwnrE27mub0.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Serif'), local('PTSerif-Regular'), url(http://fonts.gstatic.com/s/ptserif/v8/I-OtoJZa3TeyH6D9oli3ifesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Serif Bold'), local('PTSerif-Bold'), url(http://fonts.gstatic.com/s/ptserif/v8/QABk9IxT-LFTJ_dQzv7xpDTOQ_MqJVwkKsUn0wKzc2I.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Serif Bold'), local('PTSerif-Bold'), url(http://fonts.gstatic.com/s/ptserif/v8/QABk9IxT-LFTJ_dQzv7xpDUj_cnvWIuuBMVgbX098Mw.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Serif Bold'), local('PTSerif-Bold'), url(http://fonts.gstatic.com/s/ptserif/v8/QABk9IxT-LFTJ_dQzv7xpCYE0-AqJ3nfInTTiDXDjU4.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Serif Bold'), local('PTSerif-Bold'), url(http://fonts.gstatic.com/s/ptserif/v8/QABk9IxT-LFTJ_dQzv7xpI4P5ICox8Kq3LLUNMylGO4.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Serif Italic'), local('PTSerif-Italic'), url(http://fonts.gstatic.com/s/ptserif/v8/O_WhD9hODL16N4KLHLX7xSEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Serif Italic'), local('PTSerif-Italic'), url(http://fonts.gstatic.com/s/ptserif/v8/3Nwg9VzlwLXPq3fNKwVRMCEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Serif Italic'), local('PTSerif-Italic'), url(http://fonts.gstatic.com/s/ptserif/v8/b31S45a_TNgaBApZhTgE6CEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Serif Italic'), local('PTSerif-Italic'), url(http://fonts.gstatic.com/s/ptserif/v8/03aPdn7fFF3H6ngCgAlQzPk_vArhqVIZ0nv9q090hN8.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Serif Bold Italic'), local('PTSerif-BoldItalic'), url(http://fonts.gstatic.com/s/ptserif/v8/Foydq9xJp--nfYIx2TBz9ede9INZm0R8ZMJUtfOsxrw.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Serif Bold Italic'), local('PTSerif-BoldItalic'), url(http://fonts.gstatic.com/s/ptserif/v8/Foydq9xJp--nfYIx2TBz9bpHcMS0zZe4mIYvDKG2oeM.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Serif Bold Italic'), local('PTSerif-BoldItalic'), url(http://fonts.gstatic.com/s/ptserif/v8/Foydq9xJp--nfYIx2TBz9RHJTnCUrjaAm2S9z52xC3Y.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Serif Bold Italic'), local('PTSerif-BoldItalic'), url(http://fonts.gstatic.com/s/ptserif/v8/Foydq9xJp--nfYIx2TBz9YWiMMZ7xLd792ULpGE4W_Y.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n\n"
  },
  {
    "path": "examples/2D-navigation/css/presentation.css",
    "content": "/*\n  A common approach is to use googleapis.com to generate css for the webfonts you want to use.\n  The downside of this approach is that you have to be online. So below I have simply saved\n  the output of the googleapis url into a file. Then you of course also have to make sure\n  the webfonts are locally installed to make offline usage work. For Ubuntu (or Debian) I\n  successfully used the script from here to do that: \n  http://www.webupd8.org/2011/01/automatically-install-all-google-web.html\n*/\n\n/* @import url(http://fonts.googleapis.com/css?family=Open+Sans:regular,semibold,italic,italicsemibold|PT+Sans:400,700,400italic,700italic|PT+Serif:400,700,400italic,700italic|Cutive+Mono); */\n@import url(fonts.css);\n\n\n\n/*\n    We display a fallback message for users with browsers that don't support\n    all the features required by it. All of the content will be still fully \n    accessible for them, but some more advanced effects would be missing.\n    When impress.js detects that browser supports all necessary CSS3 features, \n    the fallback-message style is hidden.\n*/\n\n.fallback-message {\n    font-family: sans-serif;\n    line-height: 1.3;\n\n    width: 780px;\n    padding: 10px 10px 0;\n    margin: 20px auto;\n\n    border: 1px solid #E4C652;\n    border-radius: 10px;\n    background: #EEDC94;\n}\n\n.fallback-message p {\n    margin-bottom: 10px;\n}\n\n.impress-supported .fallback-message {\n    display: none;\n}\n\n\n/*\n  The body background is the bacgkround of \"everything\". Many\n  impress.js tools call it the \"surface\". It could also be a\n  picture or pattern, but we leave it as light gray.\n*/\n\nbody {\n    font-family: 'PT Sans', sans-serif;\n    min-height: 740px;\n\n    background: #aaccbb;\n    color: #ff4466;\n}\n\n/*\n    Now let's style the presentation steps.\n*/\n\n.step {\n    position: relative;\n    display: block;\n\n    width: 900px;\n    height: 700px;\n    margin: 20px auto;\n    padding: 40px 60px;\n\n    text-shadow: 0 2px 2px rgba(0, 10, 0, .5);\n\n    font-family: 'Open Sans', Arial, sans-serif;\n    font-size: 30px;\n    letter-spacing: -1px;\n\n}\n/*\n    Make inactive steps a little bit transparent.\n*/\n.impress-enabled .step {\n    margin: 0;\n    opacity: 0.3;\n    transition:         opacity 1s;\n}\n\n.impress-enabled .step.active { opacity: 1 }\n\nh1, \nh2, \nh3 {\n    margin-bottom: 0.5em;\n    margin-top: 0.5em;\n    text-align: center;\n}\n\np {\n    text-align: center;\n    margin: 0.7em;\n}\n\nli {\n    margin: 0.2em;    \n}\n\n/* Highlight.js used for coloring pre > code blocks. */\npre > code {\n    font-size: 14px;\n    text-shadow: 0 0 0 rgba(0, 0, 0, 0);\n}\n\n/* Inline code, no Highlight.js */\ncode {\n    font-family: \"Cutive mono\",\"Courier New\", monospace;\n}\n\n\na {\n    color: inherit;\n    text-decoration: none;\n    padding: 0 0.1em;\n    background: rgba(200,200,200,0.3);\n    text-shadow: -1px 1px 2px rgba(100,100,100,0.9);\n    border-radius: 0.2em;\n    border-bottom: 1px solid rgba(100,100,100,0.4);\n    border-left:   1px solid rgba(100,100,100,0.4);\n\n    transition:         0.5s;\n}\na:hover,\na:focus {\n    background: rgba(200,200,200,1);\n    text-shadow: -1px 1px 2px rgba(100,100,100,0.5);\n}\n\nblockquote {\n    font-family: 'PT Serif';\n    font-style: italic;\n    font-weight: 400;    \n}\n\nem {\n    text-shadow: 0 2px 2px rgba(0, 0, 0, .3);    \n}\n\nstrong {\n    text-shadow: -1px 1px 2px rgba(100,100,100,0.5);\n}\n\nq {\n    font-family: 'PT Serif';\n    font-style: italic;\n    font-weight: 400;    \n    text-shadow: 0 2px 2px rgba(0, 0, 0, .3);\n}\n\nstrike {\n    opacity: 0.7;\n}\n\nsmall {\n    font-size: 0.4em;\n}\n\nimg {\n    width: 300px\n}\n\n/****************** Background images **********************************************/\n\n\nimg.bg {\n    position: fixed;\n    z-index: -100;\n    opacity: 0;\n    height: 50%;\n    width: auto;\n    transition:  opacity 2s;\n}\n\n#applepie-image {\n    left: 0px;\n    bottom: 0px;\n}\n\nbody.impress-on-applepie #applepie-image,\nbody.impress-on-applepie-pro #applepie-image,\nbody.impress-on-applepie-con #applepie-image,\nbody.impress-on-conclusion #applepie-image,\nbody.impress-on-overview #applepie-image {\n    opacity: 0.7;\n    transition:  opacity 2s;\n}\n\n#icecream-image {\n    right: 0px;\n    top: 0px;\n}\n\nbody.impress-on-icecream #icecream-image,\nbody.impress-on-icecream-pro #icecream-image,\nbody.impress-on-icecream-con #icecream-image,\nbody.impress-on-conclusion #icecream-image,\nbody.impress-on-overview #icecream-image {\n    opacity: 0.7;\n    transition:  opacity 2s;\n}\n\n#crisps-image {\n    right: 0px;\n    bottom: 0px;\n}\n\nbody.impress-on-crisps #crisps-image,\nbody.impress-on-crisps-pro #crisps-image,\nbody.impress-on-crisps-con #crisps-image,\nbody.impress-on-conclusion #crisps-image,\nbody.impress-on-overview #crisps-image {\n    opacity: 0.7;\n    transition:  opacity 2s;\n}\n\n\n/*************** Slide specific things ****************************/\n\n#image-credits {\n    color: #779988;\n}"
  },
  {
    "path": "examples/2D-navigation/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\" />\n    <title>Desserts (2D navigation demo)</title>\n    <meta name=\"description\" content=\"2D navigation demo\" />\n    <meta name=\"author\" content=\"Henrik Ingo\" />\n    <link href=\"../../css/impress-common.css\" rel=\"stylesheet\" />\n    <link href=\"css/presentation.css\" rel=\"stylesheet\" />\n</head>\n<body class=\"impress-not-supported\">\n<div class=\"fallback-message\">\n    <p>Your browser <b>doesn't support the features required</b> by impress.js, so you are presented with a simplified version of this presentation.</p>\n    <p>For the best experience please use the latest <b>Chrome</b>, <b>Safari</b> or <b>Firefox</b> browser.</p>\n</div>\n\n<!-- Some images that are fixed to background in the css -->\n<img id=\"applepie-image\" class=\"bg\" src=\"images/6296334551_b3d5c27823_b.png\">\n<img id=\"icecream-image\" class=\"bg\" src=\"images/35535918670_f1d12627ff_o.png\">\n<img id=\"crisps-image\" class=\"bg\" src=\"images/6636957665_5e7c4a79de_o.png\">\n\n\n\n<div id=\"impress\" data-transition-duration=\"1000\">\n\n    <div class=\"step\" data-scale=\"2\" data-x=\"-500\" data-y=\"-500\">\n        <h1>2D navigation</h1>\n\n        <ul>\n        <li>Impress.js allows you to layout your presentation in a 3D space</li>\n        <li><a href=\"https://github.com/impress/impress.js/tree/master/src/plugins/goto\">The\n           goto plugin</a> allows you to specify\n           non-linear navigation!</li>\n        <li>This demo can be navigated by\n            <ul>\n            <li>continuously pressing Space</li>\n            <li>continuously pressing Right Arrow</li>\n            <li>continuously pressing Down Arrow</li>\n            <li>(or freely, pressing Up, Down, Right, Left as you choose)</li>\n            </ul>\n        <li>It's up to you to decide which is the better structure</li>\n        </li>\n    </div>\n\n    <div id=\"bm0\" class=\"step\" data-scale=\"1\" data-rel-x=\"1500\" data-rel-y=\"0\"\n\t data-bookmark-key-list=\"0\">\n        <h1>Using bookmark hotkeys</h1>\n        <ul>\n        <li><a href=\"https://github.com/impress/impress.js/tree/master/src/plugins/bookmark\">The\n           bookmark plugin</a> also allows you to specify\n           non-linear navigation, in a different way.</li>\n        <li>This demo can <em>also</em> be navigated by\n            <ul>\n            <li>pressing 1 2 3 4 5 6 7 8 9 to fast travel directly</li>\n            <li>pressing J J J, K K K, L L L to cycle vertically</li>\n            <li>pressing U U U, I I I, O O O to cycle horizontally</li>\n            <li>pressing Z or [ to zoom out to the full view</li>\n            <li>pressing 0 to come back to this text</li>\n            </ul>\n        </li>\n        <li>It's up to you to decide which is the better structure</li>\n        </ul>\n    </div>\n\n    <div id=\"contents\" class=\"step\" data-rel-x=\"0\" data-rel-y=\"1500\" data-scale=\"1\"\n\t data-bookmark-key-list=\"w\" >\n        <h1>Choosing a treat</h1>\n\n        <ul>\n            <li>You can choose your preferred treat from:\n                <ul>\n                <li>Ice cream</li>\n                <li>Crisps</li>\n                <li>Apple pie</li>\n                </ul>\n            </li>\n            <li>We will make a structured pro's &amp; con's analysis to arrive at a conclusion</li>\n        </ul>\n    </div>\n\n    <!-- Ice cream slides (3) -->\n    <div id=\"icecream\" class=\"step\" data-x=\"2000\" data-y=\"2000\"\n         data-goto-key-list=\"ArrowUp ArrowDown ArrowLeft ArrowRight\"\n         data-goto-next-list=\"contents icecream-pro contents crisps\"\n\t data-bookmark-key-list=\"7 u j\" >\n        <h1>Ice cream</h1>\n\n        <ul>\n            <li>Cold</li>\n            <li>Creamy</li>\n            <li>Vanilla or flavored</li>\n            <li>Caramel sauce, jams &amp; other toppings</li>\n        </ul>\n    </div>\n\n    <div id=\"icecream-pro\" class=\"step\" data-rel-x=\"0\" data-rel-y=\"1000\"\n         data-goto-key-list=\"ArrowUp ArrowDown ArrowLeft ArrowRight\"\n         data-goto-next-list=\"icecream icecream-con applepie crisps-pro\"\n\t data-bookmark-key-list=\"4 i j\" >\n        <h1>Ice cream: Pro's</h1>\n\n        <ul>\n            <li>Great for dessert or snack</li>\n            <li>Great in the Summer</li>\n        </ul>\n    </div>\n\n\n    <div id=\"icecream-con\" class=\"step\" data-rel-x=\"0\" data-rel-y=\"1000\"\n         data-goto-key-list=\"ArrowUp ArrowDown ArrowLeft ArrowRight\"\n         data-goto-next-list=\"icecream-pro crisps applepie-pro crisps-con\"\n\t data-bookmark-key-list=\"1 o j\" >\n        <h1>Ice cream: Con's</h1>\n\n        <ul>\n            <li>Not so great in the Winter</li>\n            <li>If you're allergic to lactose/milk</li>\n            <li>Diet alternatives are not real ice cream</li>\n        </ul>\n    </div>\n\n\n    <!-- Crisps slides (3) -->\n    <div id=\"crisps\" class=\"step\" data-x=\"3500\" data-y=\"2000\"\n         data-goto-key-list=\"ArrowUp ArrowDown ArrowLeft ArrowRight\"\n         data-goto-next-list=\"icecream-con crisps-pro icecream applepie\"\n\t data-bookmark-key-list=\"8 u k\" >\n        <h1>Crisps</h1>\n\n        <ul>\n            <li>Potatoes fried in oil and salted</li>\n            <li>Various flavors</li>\n            <li>Dips</li>\n            <li>Can be used as ingredient in subs (Cliff Huxtable style)</li>\n        </ul>\n    </div>\n\n    <div id=\"crisps-pro\" class=\"step\" data-rel-x=\"0\" data-rel-y=\"1000\"\n         data-goto-key-list=\"ArrowUp ArrowDown ArrowLeft ArrowRight\"\n         data-goto-next-list=\"crisps crisps-con icecream-pro applepie-pro\"\n\t data-bookmark-key-list=\"5 i k\" >\n        <h1>Crisps: Pro's</h1>\n\n        <ul>\n            <li>Simple yet tasty concept</li>\n            <li>Great for snack</li>\n            <li>Salty / spicy (not sweet)</li>\n            <li>Finger food</li>\n            <li>Diet alternatives are often ok</li>\n        </ul>\n    </div>\n\n\n    <div id=\"crisps-con\" class=\"step\" data-rel-x=\"0\" data-rel-y=\"1000\"\n         data-goto-key-list=\"ArrowUp ArrowDown ArrowLeft ArrowRight\"\n         data-goto-next-list=\"crisps-pro applepie icecream-con applepie-con\"\n\t data-bookmark-key-list=\"2 o k\" >\n        <h1>Crisps: Con's</h1>\n\n        <ul>\n            <li>Commonly not used as dessert</li>\n            <li>Not sweet</li>\n        </ul>\n    </div>\n\n\n    <!-- Apple pie slides (3) -->\n    <div id=\"applepie\" class=\"step\" data-x=\"5000\" data-y=\"2000\"\n         data-goto-key-list=\"ArrowUp ArrowDown ArrowLeft ArrowRight\"\n         data-goto-next-list=\"crisps-con applepie-pro crisps icecream-pro\"\n\t data-bookmark-key-list=\"9 u l\" >\n        <h1>Apple pie</h1>\n\n        <ul>\n            <li>Apple's in a pie</li>\n            <li>Many recipes exist. (Grandma's is the best.)</li>\n            <li>Vanilla sauce or cream on top</li>\n        </ul>\n    </div>\n\n    <div id=\"applepie-pro\" class=\"step\" data-rel-x=\"0\" data-rel-y=\"1000\"\n         data-goto-key-list=\"ArrowUp ArrowDown ArrowLeft ArrowRight\"\n         data-goto-next-list=\"applepie applepie-con crisps-pro icecream-con\"\n\t data-bookmark-key-list=\"6 i l\" >\n        <h1>Apple pie: Pro's</h1>\n\n        <ul>\n            <li>Great for dessert</li>\n            <li>Or just with a cup of tea or glass of milk</li>\n            <li>Best when warm</li>\n        </ul>\n    </div>\n\n\n    <div id=\"applepie-con\" class=\"step\" data-rel-x=\"0\" data-rel-y=\"1000\"\n         data-goto-key-list=\"ArrowUp ArrowDown ArrowLeft ArrowRight\"\n         data-goto-next-list=\"applepie-pro conclusion crisps-con conclusion\"\n\t data-bookmark-key-list=\"3 o l\" >\n        <h1>Apple pie: Con's</h1>\n\n        <ul>\n            <li>I'm allergic to apple (but a small slice is worth it)</li>\n            <li>Not finger food</li>\n        </ul>\n    </div>\n\n\n    <div id=\"conclusion\" class=\"step\" data-rel-x=\"1000\" data-rel-y=\"1000\"\n\t data-bookmark-key-list=\"q\" >\n        <h1>Conclusion</h1>\n\n        <p>Can I choose all three ;-)</p>\n\n        <p style=\"font-size: small; position: absolute; bottom: 30px; left: 300px;\" id=\"image-credits\"\n        >Image credits: <a href=\"https://www.flickr.com/photos/reimagingerica/35535918670\">reimagingerica@Flickr</a>, \n                        <a href=\"https://www.flickr.com/photos/mixedmolly/6636957665\">mixedmolly@Flickr</a>,\n                        <a href=\"https://www.flickr.com/photos/stevepj2009/6296334551\">stevepj2009@Flickr</a> </p>\n    </div>\n\n    <div id=\"overview\" class=\"step\" data-x=\"3000\" data-y=\"2000\" data-scale=\"9\" style=\"pointer-events: none;\"\n\t data-bookmark-key-list=\"z [\" >\n    </div>\n</div>\n\n<div id=\"impress-toolbar\"></div>\n\n<div class=\"impress-progressbar\"><div></div></div>\n<div class=\"impress-progress\"></div>\n\n<div id=\"impress-help\"></div>\n\n<script type=\"text/javascript\" src=\"../../js/impress.js\"></script>\n<script>impress().init();</script>\n\n</body>\n</html>\n"
  },
  {
    "path": "examples/3D-positions/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>relative rotations</title>\n    <link href=\"../../css/impress-common.css\" rel=\"stylesheet\" />\n    <style type=\"text/css\" media=\"screen\">\n#overview {\n    background: none;\n    border: none;\n    box-shadow: none;\n    width: 1800px;\n    height: 1300px;\n}\n\n#overview div {\n    width: 100%;\n    height: 100%;\n}\n\n.step {\n    position: relative;\n    width: 1000px;\n    height: 1000px;\n    padding: 40px 60px;\n    margin: 20px auto;\n\n    box-sizing:         border-box;\n\n    line-height: 1.5;\n\n    background-color: yellow;\n    border-radius: 10px;\n    box-shadow: 0 2px 6px rgba(0, 0, 0, .1);\n\n    text-shadow: 0 2px 2px rgba(0, 0, 0, .1);\n\n    font-family: 'Open Sans', Arial, sans-serif;\n    font-size: 40pt;\n    letter-spacing: -1px;\n    border: solid 2px red;\n\n    opacity: 40%;\n}\n\n.step.active {\n    opacity: 100%;\n}\n\n.step.ring2 {\n    background-color: cyan;\n}\n\n.step.box {\n    background-color: purple;\n    opacity: 70%;\n}\n\n.step.box1 {\n    background-color: lightblue;\n}\n    </style>\n  </head>\n\n  <body class=\"impress-not-supported\">\n    <div id=\"impress\" data-width=\"2000\" data-height=\"1500\">\n      <div id=\"overview\" class=\"step overview\" data-rel-position=\"relative\" data-x=\"-1000\" data-y=\"-1500\" data-z=\"100\" data-scale=\"3\" data-rotate-x=\"45\" data-rotate-y=\"10\">\n          <div>\n              <h2>Demo of <code>data-rel-position</code></h2>\n              <p>This demo use <code>data-rel-position=\"relative\"</code><br>\n                  and <code>data-rel-rotate-x/y/z</code><br>\n                  to easy 3D positioning of slides.</p>\n          </div>\n      </div>\n\n      <div id=\"box-front\" class=\"step box\" data-x=\"-3000\" data-y=\"0\" data-z=\"0\" data-rotate-x=\"0\" data-rotate-y=\"0\" data-rotate-z=\"0\">Front\n          <p>There's two nested box here.</p>\n      </div>\n      <div id=\"box-front1\" class=\"step box1\" data-rel-reset data-rel-z=\"-200\" data-scale=\"0.6\">Inside Front</div>\n      <div id=\"box-right1\" class=\"step box1\" data-rel-reset data-rel-x=\"300\" data-rel-z=\"-300\" data-rel-rotate-y=\"90\" data-scale=\"0.6\">Inside Right</div>\n      <div id=\"box-right\" class=\"step box\" data-rel-reset data-rel-z=\"200\">Right</div>\n      <div id=\"box-back\" class=\"step box\" data-rel-reset data-rel-x=\"500\" data-rel-z=\"-500\" data-rel-rotate-y=\"90\">Back</div>\n      <div id=\"box-back1\" class=\"step box1\" data-rel-reset data-rel-z=\"-200\" data-scale=\"0.6\">Inside Back</div>\n      <div id=\"box-top1\" class=\"step box1\" data-rel-reset data-rel-y=\"-300\" data-rel-z=\"-300\" data-rel-rotate-x=\"90\" data-scale=\"0.6\">Inside Top</div>\n      <div id=\"box-top\" class=\"step box\" data-rel-reset data-rel-z=\"200\">Top</div>\n      <div id=\"box-left\" class=\"step box\" data-rel-reset data-rel-x=\"500\" data-rel-z=\"-500\" data-rel-rotate-y=\"90\">Left</div>\n      <div id=\"box-left1\" class=\"step box1\" data-rel-reset data-rel-z=\"-200\" data-scale=\"0.6\">Inside Left</div>\n      <div id=\"box-bottom1\" class=\"step box1\" data-rel-reset data-rel-x=\"300\" data-rel-z=\"-300\" data-rel-rotate-y=\"90\" data-scale=\"0.6\">Inside Bottom</div>\n      <div id=\"box-bottom\" class=\"step box\" data-rel-reset data-rel-z=\"200\">Bottom</div>\n\n      <div id=\"ring1-1\" class=\"step\" data-rel-reset=\"all\" data-x=\"0\" data-y=\"0\" data-z=\"0\" data-rotate-y=\"10\">\n        <p>Slide one</p>\n        <p>This is a ring of 8 slides.</p>\n        <p>It's easy constucted with data-rel-position=\"relative\" without calculates the coordinates of all slides.</p>\n      </div>\n\n      <div id=\"ring1-2\" class=\"step\" data-rel-rotate-y=\"45\" data-rel-z=\"-354\" data-rel-x=\"854\">\n        <p>Slide two</p>\n        <p>The position of this slide is calculated as relatived position and rotation of the first slide.</p>\n        <p>The following slides don't need to set any position attributes, they are inherit from this slide.</p>\n      </div>\n\n      <div id=\"ring1-3\" class=\"step\">\n        <p>Slide three</p>\n      </div>\n\n      <div id=\"ring1-4\" class=\"step\">\n        <p>Slide four</p>\n      </div>\n\n      <div id=\"ring1-5\" class=\"step\">\n        <p>Slide five</p>\n      </div>\n\n      <div id=\"ring1-6\" class=\"step\">\n        <p>Slide six</p>\n      </div>\n\n      <div id=\"ring1-7\" class=\"step\">\n        <p>Slide seven</p>\n      </div>\n\n      <div id=\"ring1-8\" class=\"step\">\n        <p>Slide eight</p>\n      </div>\n\n      <div id=\"ring2-1\" class=\"step ring2\" data-rel-reset=\"all\" data-x=\"-500\" data-y=\"0\" data-z=\"-1514\" data-rotate-x=\"90\" data-rotate-y=\"270\" data-rotate-z=\"0\">\n        <p>Slide one</p>\n        <p>This is another ring of slides.</p>\n        <p>Except for the this slide, its code is just cloned from the yellow ring.</p>\n        <p>Just change the position of first slide, all the following slides are position relatived to it.</p>\n      </div>\n\n      <div id=\"ring2-2\" class=\"step ring2\" data-rel-rotate-y=\"45\" data-rel-z=\"-354\" data-rel-x=\"854\" data-rel-y=\"0\">\n        <p>Slide two</p>\n      </div>\n\n      <div id=\"ring2-3\" class=\"step ring2\">\n        <p>Slide three</p>\n      </div>\n\n      <div id=\"ring2-4\" class=\"step ring2\">\n        <p>Slide four</p>\n      </div>\n\n      <div id=\"ring2-5\" class=\"step ring2\">\n        <p>Slide five</p>\n      </div>\n\n      <div id=\"ring2-6\" class=\"step ring2\">\n        <p>Slide six</p>\n      </div>\n\n      <div id=\"ring2-7\" class=\"step ring2\">\n        <p>Slide seven</p>\n      </div>\n\n      <div id=\"ring2-8\" class=\"step ring2\">\n        <p>Slide eight</p>\n      </div>\n    </div>\n\n    <div id=\"impress-toolbar\"></div>\n    <div id=\"impress-help\"></div>\n\n    <script type=\"text/javascript\" src=\"../../js/impress.js\"></script>\n    <script>impress().init();</script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/3D-rotations/css/3D-rotations.css",
    "content": "@import url(fonts.css);\n\n\n\n/*  Fallback message */\n\n.fallback-message {\n    font-family: sans-serif;\n    line-height: 1.3;\n\n    width: 780px;\n    padding: 10px 10px 0;\n    margin: 20px auto;\n\n    border: 1px solid #E4C652;\n    border-radius: 10px;\n    background: #EEDC94;\n}\n\n.fallback-message p {\n    margin-bottom: 10px;\n}\n\n.impress-supported .fallback-message {\n    display: none;\n}\n\n\n/* Body & steps */\nbody {\n    font-family: 'PT Sans', sans-serif;\n    min-height: 740px;\n\n    background: #00000f;\n    color: rgb(102, 102, 102);\n}\n\n.step {\n    position: relative;\n    width: 700px;\n    height: 700px;\n    padding: 40px 60px;\n    margin: 20px auto;\n\n    box-sizing:         border-box;\n\n    line-height: 1.5;\n\n    background-color: white;\n    border-radius: 10px;\n    box-shadow: 0 2px 6px rgba(0, 0, 0, .1);\n\n    text-shadow: 0 2px 2px rgba(0, 0, 0, .1);\n\n    font-family: 'Open Sans', Arial, sans-serif;\n    font-size: 40pt;\n    letter-spacing: -1px;\n\n}\n\n/* Overview step has no background or border */\n\n.overview {\n    background-color: transparent;\n    border: none;\n    box-shadow: none;\n    pointer-events: none;\n    display: none;\n}\n.overview.active {\n    display: block;\n    pointer-events: auto;\n}\n\n/*\n    Make inactive steps a little bit transparent.\n*/\n.impress-enabled .step {\n    margin: 0;\n    opacity: 0.1;\n    transition:         opacity 1s;\n}\n\n.impress-enabled .step.active { opacity: 1 }\n\n\n/* Content */\n\nh1, \nh2, \nh3 {\n    margin-bottom: 0.5em;\n    margin-top: 0.5em;\n    text-align: center;\n}\n\np {\n    margin: 0.7em;\n}\n\nli {\n    margin: 0.2em;    \n}\n\n/* Highlight.js used for coloring pre > code blocks. */\npre > code {\n    font-size: 14px;\n    text-shadow: 0 0 0 rgba(0, 0, 0, 0);\n}\n\n/* Inline code, no Highlight.js */\ncode {\n    font-family: \"Cutive mono\",\"Courier New\", monospace;\n}\n\n\na {\n    color: inherit;\n    text-decoration: none;\n    padding: 0 0.1em;\n    background: rgba(200,200,200,0.2);\n    text-shadow: -1px 1px 2px rgba(100,100,100,0.9);\n    border-radius: 0.2em;\n    border-bottom: 1px solid rgba(100,100,100,0.2);\n    border-left:   1px solid rgba(100,100,100,0.2);\n\n    transition:         0.5s;\n}\na:hover,\na:focus {\n    background: rgba(200,200,200,1);\n    text-shadow: -1px 1px 2px rgba(100,100,100,0.5);\n}\n\nblockquote {\n    font-family: 'PT Serif';\n    font-style: italic;\n    font-weight: 400;    \n}\n\nem {\n    text-shadow: 0 2px 2px rgba(0, 0, 0, .3);    \n}\n\nstrong {\n    text-shadow: -1px 1px 2px rgba(100,100,100,0.5);\n}\n\nq {\n    font-family: 'PT Serif';\n    font-style: italic;\n    font-weight: 400;    \n    text-shadow: 0 2px 2px rgba(0, 0, 0, .3);\n}\n\nstrike {\n    opacity: 0.7;\n}\n\nsmall {\n    font-size: 0.4em;\n}\n\n/* Styles specific to each step */\n\n#overview2 {\n    font-size: 20pt;\n    padding-left: 200px;\n    text-align: right;\n}"
  },
  {
    "path": "examples/3D-rotations/css/fonts.css",
    "content": "/* latin-ext */\n@font-face {\n  font-family: 'Cutive Mono';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Cutive Mono'), local('CutiveMono-Regular'), url(http://fonts.gstatic.com/s/cutivemono/v4/N5odNRruTwjvCM8y77PhQSYE0-AqJ3nfInTTiDXDjU4.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'Cutive Mono';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Cutive Mono'), local('CutiveMono-Regular'), url(http://fonts.gstatic.com/s/cutivemono/v4/N5odNRruTwjvCM8y77PhQY4P5ICox8Kq3LLUNMylGO4.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/K88pR3goAWT7BTt32Z01mxJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/RjgO7rYTmqiVp7vzi-Q5URJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/LWCjsQkB6EMdfHrEVqA1KRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/xozscpT2726on7jbcb_pAhJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0370-03FF;\n}\n/* vietnamese */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/59ZRklaO5bWGqF5A9baEERJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/u-WUoqrET9fUeobQW7jkRRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/cJZKeOuBrn4kERxqtaUH3VtXRa8TVwTICgirnJhmVJw.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSq-j2U0lmluP9RWlSytm3ho.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSpX5f-9o1vgP2EXwfjgl7AY.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNShWV49_lSm1NYrwo-zkhivY.woff2) format('woff2');\n  unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSqaRobkAwv3vxw3jMhVENGA.woff2) format('woff2');\n  unicode-range: U+0370-03FF;\n}\n/* vietnamese */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSv8zf_FOSsgRmwsS7Aa9k2w.woff2) format('woff2');\n  unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSj0LW-43aMEzIO6XUTLjad8.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSugdm0LZdjqr5-oayXSOefg.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBjTOQ_MqJVwkKsUn0wKzc2I.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBjUj_cnvWIuuBMVgbX098Mw.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBkbcKLIaa1LC45dFaAfauRA.woff2) format('woff2');\n  unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBmo_sUJ8uO4YLWRInS22T3Y.woff2) format('woff2');\n  unicode-range: U+0370-03FF;\n}\n/* vietnamese */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBr6up8jxqWt8HVA3mDhkV_0.woff2) format('woff2');\n  unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBiYE0-AqJ3nfInTTiDXDjU4.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBo4P5ICox8Kq3LLUNMylGO4.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxmgpAmOCqD37_tyH_8Ri5MM.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxsPNMTLbnS9uQzHQlYieHUU.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxgyhumQnPMBCoGYhRaNxyyY.woff2) format('woff2');\n  unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxhUVAXEdVvYDDqrz3aeR0Yc.woff2) format('woff2');\n  unicode-range: U+0370-03FF;\n}\n/* vietnamese */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxlf4y_3s5bcYyyLIFUSWYUU.woff2) format('woff2');\n  unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxnywqdtBbUHn3VPgzuFrCy8.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxl2umOyRU7PgRiv8DXcgJjk.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Sans'), local('PTSans-Regular'), url(http://fonts.gstatic.com/s/ptsans/v8/JX7MlXqjSJNjQvI4heMMGvY6323mHUZFJMgTvxaG2iE.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Sans'), local('PTSans-Regular'), url(http://fonts.gstatic.com/s/ptsans/v8/vtwNVMP8y9C17vLvIBNZI_Y6323mHUZFJMgTvxaG2iE.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Sans'), local('PTSans-Regular'), url(http://fonts.gstatic.com/s/ptsans/v8/9kaD4V2pNPMMeUVBHayd7vY6323mHUZFJMgTvxaG2iE.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Sans'), local('PTSans-Regular'), url(http://fonts.gstatic.com/s/ptsans/v8/ATKpv8nLYAKUYexo8iqqrg.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Sans Bold'), local('PTSans-Bold'), url(http://fonts.gstatic.com/s/ptsans/v8/kTYfCWJhlldPf5LnG4ZnHCEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Sans Bold'), local('PTSans-Bold'), url(http://fonts.gstatic.com/s/ptsans/v8/g46X4VH_KHOWAAa-HpnGPiEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Sans Bold'), local('PTSans-Bold'), url(http://fonts.gstatic.com/s/ptsans/v8/hpORcvLZtemlH8gI-1S-7iEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Sans Bold'), local('PTSans-Bold'), url(http://fonts.gstatic.com/s/ptsans/v8/0XxGQsSc1g4rdRdjJKZrNPk_vArhqVIZ0nv9q090hN8.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Sans Italic'), local('PTSans-Italic'), url(http://fonts.gstatic.com/s/ptsans/v8/GpWpM_6S4VQLPNAQ3iWvVRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Sans Italic'), local('PTSans-Italic'), url(http://fonts.gstatic.com/s/ptsans/v8/7dSh6BcuqDLzS2qAASIeuhJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Sans Italic'), local('PTSans-Italic'), url(http://fonts.gstatic.com/s/ptsans/v8/DVKQJxMmC9WF_oplMzlQqRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Sans Italic'), local('PTSans-Italic'), url(http://fonts.gstatic.com/s/ptsans/v8/PIPMHY90P7jtyjpXuZ2cLFtXRa8TVwTICgirnJhmVJw.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Sans Bold Italic'), local('PTSans-BoldItalic'), url(http://fonts.gstatic.com/s/ptsans/v8/lILlYDvubYemzYzN7GbLkK-j2U0lmluP9RWlSytm3ho.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Sans Bold Italic'), local('PTSans-BoldItalic'), url(http://fonts.gstatic.com/s/ptsans/v8/lILlYDvubYemzYzN7GbLkJX5f-9o1vgP2EXwfjgl7AY.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Sans Bold Italic'), local('PTSans-BoldItalic'), url(http://fonts.gstatic.com/s/ptsans/v8/lILlYDvubYemzYzN7GbLkD0LW-43aMEzIO6XUTLjad8.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Sans Bold Italic'), local('PTSans-BoldItalic'), url(http://fonts.gstatic.com/s/ptsans/v8/lILlYDvubYemzYzN7GbLkOgdm0LZdjqr5-oayXSOefg.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Serif'), local('PTSerif-Regular'), url(http://fonts.gstatic.com/s/ptserif/v8/5hX15RUpPERmeybVlLQEWBTbgVql8nDJpwnrE27mub0.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Serif'), local('PTSerif-Regular'), url(http://fonts.gstatic.com/s/ptserif/v8/fU0HAfLiPHGlZhZpY6M7dBTbgVql8nDJpwnrE27mub0.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Serif'), local('PTSerif-Regular'), url(http://fonts.gstatic.com/s/ptserif/v8/CPRt--GVMETgA6YEaoGitxTbgVql8nDJpwnrE27mub0.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Serif'), local('PTSerif-Regular'), url(http://fonts.gstatic.com/s/ptserif/v8/I-OtoJZa3TeyH6D9oli3ifesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Serif Bold'), local('PTSerif-Bold'), url(http://fonts.gstatic.com/s/ptserif/v8/QABk9IxT-LFTJ_dQzv7xpDTOQ_MqJVwkKsUn0wKzc2I.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Serif Bold'), local('PTSerif-Bold'), url(http://fonts.gstatic.com/s/ptserif/v8/QABk9IxT-LFTJ_dQzv7xpDUj_cnvWIuuBMVgbX098Mw.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Serif Bold'), local('PTSerif-Bold'), url(http://fonts.gstatic.com/s/ptserif/v8/QABk9IxT-LFTJ_dQzv7xpCYE0-AqJ3nfInTTiDXDjU4.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Serif Bold'), local('PTSerif-Bold'), url(http://fonts.gstatic.com/s/ptserif/v8/QABk9IxT-LFTJ_dQzv7xpI4P5ICox8Kq3LLUNMylGO4.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Serif Italic'), local('PTSerif-Italic'), url(http://fonts.gstatic.com/s/ptserif/v8/O_WhD9hODL16N4KLHLX7xSEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Serif Italic'), local('PTSerif-Italic'), url(http://fonts.gstatic.com/s/ptserif/v8/3Nwg9VzlwLXPq3fNKwVRMCEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Serif Italic'), local('PTSerif-Italic'), url(http://fonts.gstatic.com/s/ptserif/v8/b31S45a_TNgaBApZhTgE6CEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Serif Italic'), local('PTSerif-Italic'), url(http://fonts.gstatic.com/s/ptserif/v8/03aPdn7fFF3H6ngCgAlQzPk_vArhqVIZ0nv9q090hN8.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Serif Bold Italic'), local('PTSerif-BoldItalic'), url(http://fonts.gstatic.com/s/ptserif/v8/Foydq9xJp--nfYIx2TBz9ede9INZm0R8ZMJUtfOsxrw.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Serif Bold Italic'), local('PTSerif-BoldItalic'), url(http://fonts.gstatic.com/s/ptserif/v8/Foydq9xJp--nfYIx2TBz9bpHcMS0zZe4mIYvDKG2oeM.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Serif Bold Italic'), local('PTSerif-BoldItalic'), url(http://fonts.gstatic.com/s/ptserif/v8/Foydq9xJp--nfYIx2TBz9RHJTnCUrjaAm2S9z52xC3Y.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Serif Bold Italic'), local('PTSerif-BoldItalic'), url(http://fonts.gstatic.com/s/ptserif/v8/Foydq9xJp--nfYIx2TBz9YWiMMZ7xLd792ULpGE4W_Y.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n\n"
  },
  {
    "path": "examples/3D-rotations/index.html",
    "content": "<!doctype html>\n\n<!--\n  This is a simple example / template impress.js slide show. The goal is to be\n  easier to read for a first timer than the official and very feature rich\n  demo by bartaz (http://bartaz.github.io/impress.js/). It's also a very\n  traditional presentation that looks like slides (square screens with bullet\n  points...), again to make a first timer feel more at home. From this simple\n  presentation you can then go on to more powerful impress.js presentations!\n  \n  This example is hopefully helpful for people that want to create both\n  simple and (eventually) awesome presentations in impress.js and comfortable\n  doing that directly in HTML.\n  \n  By: @henrikingo (Still based on the HTML from bartaz' demo.)  \n    \n-->\n\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\" />\n    <title>A Study in 3D Rotations| by Henrik Ingo @henrikingo</title>\n    <meta name=\"description\" content=\"Explore impress.js in 3D\" />\n    <meta name=\"author\" content=\"Henrik Ingo\" />\n    <link href=\"../../css/impress-common.css\" rel=\"stylesheet\" />\n    <link href=\"css/3D-rotations.css\" rel=\"stylesheet\" />\n</head>\n\n<body class=\"impress-not-supported\">\n<div class=\"fallback-message\">\n    <p>Your browser <b>doesn't support the features required</b> by impress.js, so you are presented with a simplified version of this presentation.</p>\n    <p>For the best experience please use the latest <b>Chrome</b>, <b>Safari</b> or <b>Firefox</b> browser.</p>\n</div>\n\n<div id=\"impress\" data-transition-duration=\"2000\">\n\n\n    <div id=\"overview\" class=\"step overview\" data-x=\"1350\" data-y=\"100\" data-z=\"100\" data-scale=\"3\" data-rotate-y=\"90\">\n         <h1>A Study in 3D Rotations</h1>\n    </div>\n    <div id=\"overview2\" class=\"step overview\" data-x=\"2018\" data-y=\"106\" data-z=\"3018\" data-scale=\"2\">\n         <p>Unlike the <code>x/y/z</code> coordinates (aka translations), the <code>rotate-x/y/z</code> rotations are applied in a \n         specific order, and order matters. This demo presentation exhibits the use of the new <code>data-rotate- order</code> attribute.\n         Instead of the default \"xyz\" order, the steps use the reversed <em>\"zyx\"</em> order in applying rotations around each axis.\n         Some of the steps (<a href=\"#step-3\">3</a> &amp; <a href=\"#step-7\">7</a>), are in positions that are not possible with the default \"xyz\" order.</p>\n    </div>\n\n    <div id=\"step-1\" class=\"step\" data-x=\"0\" data-y=\"0\" data-z=\"0\"\n         data-goto-prev=\"step-8\">\n        <p>Slide one</p>\n    </div>\n\n    <div id=\"step-2\" class=\"step\" data-x=\"420\" data-y=\"-70\" data-z=\"-250\" data-rotate-z=\"45\" data-rotate-y=\"-45\" data-rotate-order=\"zyx\">\n        <p>Slide two</p>\n    </div>\n\n    <div id=\"step-3\" class=\"step\" data-x=\"700\" data-y=\"350\" data-z=\"-350\" data-rotate-z=\"90\" data-rotate-y=\"-90\" data-rotate-order=\"zyx\">\n        <p>Slide three</p>\n    </div>\n\n    <div id=\"step-4\" class=\"step\" data-x=\"422\" data-y=\"780\" data-z=\"-250\" data-rotate-z=\"135\" data-rotate-y=\"-135\" data-rotate-order=\"zyx\">\n        <p>Slide four</p>\n    </div>\n\n    <div id=\"step-5\" class=\"step\" data-x=\"0\" data-y=\"702\" data-z=\"0\" data-rotate-z=\"180\" data-rotate-y=\"-180\" data-rotate-order=\"zyx\">\n        <p>Slide five</p>\n    </div>\n\n    <div id=\"step-6\" class=\"step\" data-x=\"379\" data-y=\"780\" data-z=\"270\" data-rotate-z=\"135\" data-rotate-y=\"-225\" data-rotate-order=\"zyx\">\n        <p>Slide six</p>\n    </div>\n\n    <div id=\"step-7\" class=\"step\" data-x=\"700\" data-y=\"350\" data-z=\"350\" data-rotate-z=\"90\" data-rotate-y=\"-270\" data-rotate-order=\"zyx\">\n        <p>Slide seven</p>\n    </div>\n\n    <div id=\"step-8\" class=\"step\" data-x=\"379\" data-y=\"-70\" data-z=\"270\" data-rotate-z=\"45\" data-rotate-y=\"-315\" data-rotate-order=\"zyx\"\n         data-goto-next=\"step-1\">\n        <p>Slide eight</p>\n    </div>\n</div>\n\n<div id=\"impress-toolbar\"></div>\n<div id=\"impress-help\"></div>\n\n<script type=\"text/javascript\" src=\"../../js/impress.js\"></script>\n<script>impress().init();</script>\n\n</body>\n</html>\n"
  },
  {
    "path": "examples/classic-slides/css/classic-slides.css",
    "content": "/*\n  A common approach is to use googleapis.com to generate css for the webfonts you want to use.\n  The downside of this approach is that you have to be online. So below I have simply saved\n  the output of the googleapis url into a file. Then you of course also have to make sure\n  the webfonts are locally installed to make offline usage work. For Ubuntu (or Debian) I\n  successfully used the script from here to do that: \n  http://www.webupd8.org/2011/01/automatically-install-all-google-web.html\n*/\n\n/* @import url(https://fonts.googleapis.com/css?family=Open+Sans:regular,semibold,italic,italicsemibold|PT+Sans:400,700,400italic,700italic|PT+Serif:400,700,400italic,700italic|Cutive+Mono); */\n@import url(fonts.css);\n\n\n\n/*\n    We display a fallback message for users with browsers that don't support\n    all the features required by it. All of the content will be still fully \n    accessible for them, but some more advanced effects would be missing.\n    When impress.js detects that browser supports all necessary CSS3 features, \n    the fallback-message style is hidden.\n*/\n\n.fallback-message {\n    font-family: sans-serif;\n    line-height: 1.3;\n\n    width: 780px;\n    padding: 10px 10px 0;\n    margin: 20px auto;\n\n    border: 1px solid #E4C652;\n    border-radius: 10px;\n    background: #EEDC94;\n}\n\n.fallback-message p {\n    margin-bottom: 10px;\n}\n\n.impress-supported .fallback-message {\n    display: none;\n}\n\n\n/*\n  The body background is the bacgkround of \"everything\". Many\n  impress.js tools call it the \"surface\". It could also be a\n  picture or pattern, but we leave it as light gray.\n*/\n\nbody {\n    font-family: 'PT Sans', sans-serif;\n    min-height: 740px;\n\n    background: rgb(215, 215, 215);\n    color: rgb(70, 70, 70);\n}\n\n/*\n    Now let's style the presentation steps.\n*/\n\n.step {\n    position: relative;\n    width: 1800px;\n    padding: 60px;\n    margin: 60px auto;\n\n    box-sizing:         border-box;\n\n    font-family: 'PT Serif', georgia, serif;\n    font-size: 60px;\n    line-height: 1.5;\n}\n/*\n    Make inactive steps a little bit transparent.\n*/\n.impress-enabled .step {\n    margin: 0;\n    opacity: 0.3;\n    transition:         opacity 1s;\n}\n\n.impress-enabled .step.active { opacity: 1 }\n\n/*\n    These 'slide' step styles were heavily inspired by HTML5 Slides:\n    http://html5slides.googlecode.com/svn/trunk/styles.css\n    \n    Note that we also use a background image, again just to facilitate a common\n    feature from PowerPoint and LibreOffice worlds. In this case the background\n    image is just the impress.js favicon - as if it were a company logo or something.\n    \n*/\n.slide {\n    display: block;\n\n    width: 1850px;\n    height: 1000px;\n    padding: 40px 60px;\n\n    background-image: url(../images/background.png);\n    background-color: white;\n    border: 2px solid rgba(0, 0, 0, .3);\n    border-radius: 30px;\n    box-shadow: 0 4px 8px rgba(0, 0, 0, .1);\n\n    text-shadow: 0 3px 3px rgba(0, 0, 0, .2);\n\n    font-family: 'Open Sans', Arial, sans-serif;\n    font-size: 45px;\n    letter-spacing: -2px;\n}\n\n\n.slide h1, \n.slide h2, \n.slide h3 {\n    margin-bottom: 0.5em;\n    margin-top: 0.5em;\n    text-align: center;\n}\n\n.slide p {\n    text-align: center;\n    margin: 0.7em;\n}\n\n.slide li {\n    margin-top: 0.2em;\n    margin-bottom: 0.2em;\n    margin-left: 3em;\n    margin-right: 3em;\n}\n\n/* Highlight.js used for coloring pre > code blocks. */\n.slide pre > code {\n    font-size: 30px;\n    text-shadow: 0 0 0 rgba(0, 0, 0, 0);\n}\n\n.slide input {\n    font-size: 1em;\n}\n\n/* Inline code, no Highlight.js */\ncode {\n    font-family: \"Cutive mono\",\"Courier New\", monospace;\n}\n\n\na {\n    color: inherit;\n    text-decoration: none;\n    padding: 0 0.1em;\n    background: rgba(200,200,200,0.2);\n    text-shadow: -2px 2px 4px rgba(100,100,100,0.9);\n    border-radius: 0.2em;\n    border-bottom: 3px solid rgba(100,100,100,0.2);\n    border-left:   3px solid rgba(100,100,100,0.2);\n\n    transition:         0.5s;\n}\na:hover,\na:focus {\n    background: rgba(200,200,200,1);\n    text-shadow: -2px 2px 3px rgba(100,100,100,0.5);\n}\n\nblockquote {\n    font-family: 'PT Serif';\n    font-style: italic;\n    font-weight: 400;\n}\n\nem {\n    text-shadow: 0 6px 6px rgba(0, 0, 0, .3);\n}\n\nstrong {\n    text-shadow: -3px 3px 6px rgba(100,100,100,0.5);\n}\n\nq {\n    font-family: 'PT Serif';\n    font-style: italic;\n    font-weight: 400;    \n    text-shadow: 0 6px 6px rgba(0, 0, 0, .3);\n}\n\nstrike {\n    opacity: 0.7;\n}\n\nsmall {\n    font-size: 0.4em;\n}\n\nimg {\n    width: 600px\n}\n\ntd {\n    padding: 0.2em;\n}\n\n.slide .right {\n    float: right;\n    margin-left:  60px;\n    margin-right:   0px;\n    margin-top:    40px;\n    margin-bottom: 40px;\n}\n.slide .left {\n    float: left;\n    margin-right: 60px;\n    margin-left:    0px;\n    margin-top:    40px;\n    margin-bottom: 40px;\n}\n.slide .top {\n    position: absolute;\n    top: 40px;\n    margin-bottom:  40px;\n    margin-top:      0px;    \n}\n.slide .bottom {\n    position: absolute;\n    bottom: 40px;\n    margin-bottom:   0px;\n    margin-top:     40px;    \n}\n\n/* \n    Specific styles for: .title slides \n*/\n\n.title {\n    background-image: url(../images/background-title.png);    \n}\n\n.title h1,\n.title h2,\n.title h3 {\n    position: absolute;\n    left: 90px; /* slide width is 1800px, so this is like a 5% margin on both sides */\n    width: 90%;\n    text-align: center;\n}\n.title h1 { top: 50px; }\n.title h2 { top: 600px; }\n.title h3 { top: 800px; }\n\n\n\n/* Styles for animating the contents of a slide, such as a img, p, li or span element. ********/\n\n/*\n fly-in class starts from a position outside the slide, then flies into it's correct position.\n*/\n.future .fly-in {\n    transform: translateY(-2100px);\n    opacity: 0.0; /* Make it invisible, just so it doesn't clutter some other slide that might be in the position where we moved it */\n}\n.present .fly-in {\n    transform:         translateY(0px);\n    opacity: 1.0;\n    transition:         2s;\n}\n.past .fly-out {\n    transform:         translateY(2100px);\n    opacity: 0.0;\n    transition:         2s;\n}\n\n/*\n   Fade-in/out is a straightforward fade. Give it enough seconds that all browsers render it clearly.\n*/\n.future .fade-in {\n    opacity: 0.0;\n}\n.present .fade-in {\n    opacity: 1.0;\n    transition: 3s;\n}\n.past .fade-out {\n    opacity: 0.0;\n    transition: 3s;\n}\n/*\n    Zoom-in.\n*/\n.future  .zoom-in {\n    transform:        scale(10);\n    opacity: 0.0;    \n}\n.present .zoom-in {\n    transform:         scale(1);\n    opacity: 1.0;\n    transition: 3s;\n}\n.past  .zoom-out {\n    transform:        scale(10);\n    opacity: 0.0;    \n}\n\n/*\n    Styles for specific slides.\n*/\n\n/* The bar graph for Acme Inc profits */\n\n#acme-graph-bottom {\n    position: absolute;\n    bottom: 100px;\n    right: 200px;\n    background-color: black;\n    width: 900px;\n    height: 3px;\n}\n\n/* height: is set from javascript */\n#acme-graph-q1,\n#acme-graph-q2,\n#acme-graph-q3,\n#acme-graph-q4 {\n    border: solid 1px black;\n    width: 140px;\n    margin-left: 30px;\n    position: absolute;\n    bottom: 100px;\n}\n\n#acme-graph-q1 {\n    background-color: red;\n    right: 900px;\n}\n\n#acme-graph-q2 {\n    background-color: blue;\n    right: 700px;\n}\n\n#acme-graph-q3 {\n    background-color: green;\n    right: 500px;\n}\n\n#acme-graph-q4 {\n    background-color: purple;\n    left: 750px;\n    right: 300px;\n}\n\n/*\n    And as the last thing there is a workaround for quite strange bug.\n    It happens a lot in Chrome. I don't remember if I've seen it in Firefox.\n\n    Sometimes the element positioned in 3D (especially when it's moved back\n    along Z axis) is not clickable, because it falls 'behind' the <body>\n    element.\n\n    To prevent this, I decided to make <body> non clickable by setting\n    pointer-events property to `none` value.\n    Value if this property is inherited, so to make everything else clickable\n    I bring it back on the #impress element.\n\n    If you want to know more about `pointer-events` here are some docs:\n    https://developer.mozilla.org/en/CSS/pointer-events\n\n    There is one very important thing to notice about this workaround - it makes\n    everything 'unclickable' except what's in #impress element.\n\n    So use it wisely ... or don't use at all.\n*/\n.impress-enabled                          { pointer-events: none }\n.impress-enabled #impress                 { pointer-events: auto }\n.impress-enabled #impress-toolbar         { pointer-events: auto }\n.impress-enabled #impress-console-button  { pointer-events: auto }\n"
  },
  {
    "path": "examples/classic-slides/css/fonts.css",
    "content": "/* latin-ext */\n@font-face {\n  font-family: 'Cutive Mono';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Cutive Mono'), local('CutiveMono-Regular'), url(http://fonts.gstatic.com/s/cutivemono/v4/N5odNRruTwjvCM8y77PhQSYE0-AqJ3nfInTTiDXDjU4.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'Cutive Mono';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Cutive Mono'), local('CutiveMono-Regular'), url(http://fonts.gstatic.com/s/cutivemono/v4/N5odNRruTwjvCM8y77PhQY4P5ICox8Kq3LLUNMylGO4.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/K88pR3goAWT7BTt32Z01mxJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/RjgO7rYTmqiVp7vzi-Q5URJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/LWCjsQkB6EMdfHrEVqA1KRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/xozscpT2726on7jbcb_pAhJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0370-03FF;\n}\n/* vietnamese */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/59ZRklaO5bWGqF5A9baEERJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/u-WUoqrET9fUeobQW7jkRRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/cJZKeOuBrn4kERxqtaUH3VtXRa8TVwTICgirnJhmVJw.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSq-j2U0lmluP9RWlSytm3ho.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSpX5f-9o1vgP2EXwfjgl7AY.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNShWV49_lSm1NYrwo-zkhivY.woff2) format('woff2');\n  unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSqaRobkAwv3vxw3jMhVENGA.woff2) format('woff2');\n  unicode-range: U+0370-03FF;\n}\n/* vietnamese */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSv8zf_FOSsgRmwsS7Aa9k2w.woff2) format('woff2');\n  unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSj0LW-43aMEzIO6XUTLjad8.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSugdm0LZdjqr5-oayXSOefg.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBjTOQ_MqJVwkKsUn0wKzc2I.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBjUj_cnvWIuuBMVgbX098Mw.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBkbcKLIaa1LC45dFaAfauRA.woff2) format('woff2');\n  unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBmo_sUJ8uO4YLWRInS22T3Y.woff2) format('woff2');\n  unicode-range: U+0370-03FF;\n}\n/* vietnamese */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBr6up8jxqWt8HVA3mDhkV_0.woff2) format('woff2');\n  unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBiYE0-AqJ3nfInTTiDXDjU4.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBo4P5ICox8Kq3LLUNMylGO4.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxmgpAmOCqD37_tyH_8Ri5MM.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxsPNMTLbnS9uQzHQlYieHUU.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxgyhumQnPMBCoGYhRaNxyyY.woff2) format('woff2');\n  unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxhUVAXEdVvYDDqrz3aeR0Yc.woff2) format('woff2');\n  unicode-range: U+0370-03FF;\n}\n/* vietnamese */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxlf4y_3s5bcYyyLIFUSWYUU.woff2) format('woff2');\n  unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxnywqdtBbUHn3VPgzuFrCy8.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxl2umOyRU7PgRiv8DXcgJjk.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Sans'), local('PTSans-Regular'), url(http://fonts.gstatic.com/s/ptsans/v8/JX7MlXqjSJNjQvI4heMMGvY6323mHUZFJMgTvxaG2iE.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Sans'), local('PTSans-Regular'), url(http://fonts.gstatic.com/s/ptsans/v8/vtwNVMP8y9C17vLvIBNZI_Y6323mHUZFJMgTvxaG2iE.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Sans'), local('PTSans-Regular'), url(http://fonts.gstatic.com/s/ptsans/v8/9kaD4V2pNPMMeUVBHayd7vY6323mHUZFJMgTvxaG2iE.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Sans'), local('PTSans-Regular'), url(http://fonts.gstatic.com/s/ptsans/v8/ATKpv8nLYAKUYexo8iqqrg.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Sans Bold'), local('PTSans-Bold'), url(http://fonts.gstatic.com/s/ptsans/v8/kTYfCWJhlldPf5LnG4ZnHCEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Sans Bold'), local('PTSans-Bold'), url(http://fonts.gstatic.com/s/ptsans/v8/g46X4VH_KHOWAAa-HpnGPiEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Sans Bold'), local('PTSans-Bold'), url(http://fonts.gstatic.com/s/ptsans/v8/hpORcvLZtemlH8gI-1S-7iEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Sans Bold'), local('PTSans-Bold'), url(http://fonts.gstatic.com/s/ptsans/v8/0XxGQsSc1g4rdRdjJKZrNPk_vArhqVIZ0nv9q090hN8.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Sans Italic'), local('PTSans-Italic'), url(http://fonts.gstatic.com/s/ptsans/v8/GpWpM_6S4VQLPNAQ3iWvVRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Sans Italic'), local('PTSans-Italic'), url(http://fonts.gstatic.com/s/ptsans/v8/7dSh6BcuqDLzS2qAASIeuhJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Sans Italic'), local('PTSans-Italic'), url(http://fonts.gstatic.com/s/ptsans/v8/DVKQJxMmC9WF_oplMzlQqRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Sans Italic'), local('PTSans-Italic'), url(http://fonts.gstatic.com/s/ptsans/v8/PIPMHY90P7jtyjpXuZ2cLFtXRa8TVwTICgirnJhmVJw.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Sans Bold Italic'), local('PTSans-BoldItalic'), url(http://fonts.gstatic.com/s/ptsans/v8/lILlYDvubYemzYzN7GbLkK-j2U0lmluP9RWlSytm3ho.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Sans Bold Italic'), local('PTSans-BoldItalic'), url(http://fonts.gstatic.com/s/ptsans/v8/lILlYDvubYemzYzN7GbLkJX5f-9o1vgP2EXwfjgl7AY.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Sans Bold Italic'), local('PTSans-BoldItalic'), url(http://fonts.gstatic.com/s/ptsans/v8/lILlYDvubYemzYzN7GbLkD0LW-43aMEzIO6XUTLjad8.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Sans Bold Italic'), local('PTSans-BoldItalic'), url(http://fonts.gstatic.com/s/ptsans/v8/lILlYDvubYemzYzN7GbLkOgdm0LZdjqr5-oayXSOefg.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Serif'), local('PTSerif-Regular'), url(http://fonts.gstatic.com/s/ptserif/v8/5hX15RUpPERmeybVlLQEWBTbgVql8nDJpwnrE27mub0.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Serif'), local('PTSerif-Regular'), url(http://fonts.gstatic.com/s/ptserif/v8/fU0HAfLiPHGlZhZpY6M7dBTbgVql8nDJpwnrE27mub0.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Serif'), local('PTSerif-Regular'), url(http://fonts.gstatic.com/s/ptserif/v8/CPRt--GVMETgA6YEaoGitxTbgVql8nDJpwnrE27mub0.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Serif'), local('PTSerif-Regular'), url(http://fonts.gstatic.com/s/ptserif/v8/I-OtoJZa3TeyH6D9oli3ifesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Serif Bold'), local('PTSerif-Bold'), url(http://fonts.gstatic.com/s/ptserif/v8/QABk9IxT-LFTJ_dQzv7xpDTOQ_MqJVwkKsUn0wKzc2I.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Serif Bold'), local('PTSerif-Bold'), url(http://fonts.gstatic.com/s/ptserif/v8/QABk9IxT-LFTJ_dQzv7xpDUj_cnvWIuuBMVgbX098Mw.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Serif Bold'), local('PTSerif-Bold'), url(http://fonts.gstatic.com/s/ptserif/v8/QABk9IxT-LFTJ_dQzv7xpCYE0-AqJ3nfInTTiDXDjU4.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Serif Bold'), local('PTSerif-Bold'), url(http://fonts.gstatic.com/s/ptserif/v8/QABk9IxT-LFTJ_dQzv7xpI4P5ICox8Kq3LLUNMylGO4.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Serif Italic'), local('PTSerif-Italic'), url(http://fonts.gstatic.com/s/ptserif/v8/O_WhD9hODL16N4KLHLX7xSEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Serif Italic'), local('PTSerif-Italic'), url(http://fonts.gstatic.com/s/ptserif/v8/3Nwg9VzlwLXPq3fNKwVRMCEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Serif Italic'), local('PTSerif-Italic'), url(http://fonts.gstatic.com/s/ptserif/v8/b31S45a_TNgaBApZhTgE6CEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Serif Italic'), local('PTSerif-Italic'), url(http://fonts.gstatic.com/s/ptserif/v8/03aPdn7fFF3H6ngCgAlQzPk_vArhqVIZ0nv9q090hN8.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Serif Bold Italic'), local('PTSerif-BoldItalic'), url(http://fonts.gstatic.com/s/ptserif/v8/Foydq9xJp--nfYIx2TBz9ede9INZm0R8ZMJUtfOsxrw.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Serif Bold Italic'), local('PTSerif-BoldItalic'), url(http://fonts.gstatic.com/s/ptserif/v8/Foydq9xJp--nfYIx2TBz9bpHcMS0zZe4mIYvDKG2oeM.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Serif Bold Italic'), local('PTSerif-BoldItalic'), url(http://fonts.gstatic.com/s/ptserif/v8/Foydq9xJp--nfYIx2TBz9RHJTnCUrjaAm2S9z52xC3Y.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Serif Bold Italic'), local('PTSerif-BoldItalic'), url(http://fonts.gstatic.com/s/ptserif/v8/Foydq9xJp--nfYIx2TBz9YWiMMZ7xLd792ULpGE4W_Y.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n\n"
  },
  {
    "path": "examples/classic-slides/index.html",
    "content": "<!doctype html>\n\n<!--\n  This is a simple example / template impress.js slide show. The goal is to be\n  easier to read for a first timer than the official and very feature rich\n  demo by bartaz (http://bartaz.github.io/impress.js/). It's also a very\n  traditional presentation that looks like slides (square screens with bullet\n  points...), again to make a first timer feel more at home. From this simple\n  presentation you can then go on to more powerful impress.js presentations!\n  \n  This example is hopefully helpful for people that want to create both\n  simple and (eventually) awesome presentations in impress.js and comfortable\n  doing that directly in HTML.\n  \n  By: @henrikingo (Still based on the HTML from bartaz' demo.)  \n    \n-->\n\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\" />\n    <title>Classic Slides with impress.js | Simple example impress.js slide show | by Henrik Ingo @henrikingo</title>\n    \n    <meta name=\"description\" content=\"Simple example impress.js slide show\" />\n    <meta name=\"author\" content=\"Henrik Ingo\" />\n    <!--        \n        Impress.js doesn't depend on any external stylesheets. It adds all of the styles it needs for the\n        presentation to work. \n        \n        However, some of the `extras/` modules do come with their own CSS, and we load\n        them here. You can read about each extras module separately in their directory.\n    -->\n    <link rel=\"stylesheet\" href=\"../../extras/highlight/styles/github.css\">\n    <link rel=\"stylesheet\" href=\"../../extras/mermaid/mermaid.forest.css\">\n\n    <!-- This file contains common styles for example presentations. -->\n    <link href=\"../../css/impress-common.css\" rel=\"stylesheet\" />\n    <!--\n        This file contains styles specific for this example presentation. \n    -->\n    <link href=\"css/classic-slides.css\" rel=\"stylesheet\" />\n    \n</head>\n\n<!--\n    \n    Body element is used by impress.js to set some useful class names, that will allow you to detect\n    the support and state of the presentation in CSS or other scripts.\n    \n    First very useful class name is `impress-not-supported`. This class means, that browser doesn't\n    support features required by impress.js, so you should apply some fallback styles in your CSS.\n    It's not necessary to add it manually on this element. If the script detects that browser is not\n    good enough it will add this class, but keeping it in HTML means that users without JavaScript\n    will also get fallback styles.\n    \n    When impress.js script detects that browser supports all required features, this class name will\n    be removed.\n    \n    Another class name on body element also depends on currently active presentation step.    \n-->\n<body class=\"impress-not-supported\">\n<!--\n    This fallback message is only visible when there is `impress-not-supported` class on body.\n-->\n<div class=\"fallback-message\">\n    <p>Your browser <b>doesn't support the features required</b> by impress.js, so you are presented with a simplified version of this presentation.</p>\n    <p>For the best experience please use the latest <b>Chrome</b>, <b>Safari</b> or <b>Firefox</b> browser.</p>\n</div>\n\n<!--\n    This is the core element used by impress.js: the wrapper for your presentation steps. \n    In this element all the impress.js magic happens.\n    \n    data-transition-duration sets the time in microseconds that is used for the\n    animation when transtitioning between slides.\n    \n    The width, height, scale and perspective options define a target screen size that you should\n    design your CSS against. impress.js will automatically scale all content to different screen\n    sizes. See DOCUMENTATION.md for details. Below, I have targeted full HD screen resolution.\n    \n    data-autoplay can be used to set the time in seconds, after which presentation\n    automatically moves to next slide. It can also be set individually for each\n    slide, but here we just set a common duration for all slides.\n-->\n<div id=\"impress\"\n    data-transition-duration=\"1000\"\n\n    data-width=\"1920\"\n    data-height=\"1080\"\n    data-max-scale=\"3\"\n    data-min-scale=\"0\"\n    data-perspective=\"1000\"\n\n    data-autoplay=\"10\">\n\n    <!--\n        Each step of the presentation should be an element inside the `#impress` with a class name\n        of `step`. These step elements are positioned, rotated and scaled by impress.js, and\n        the 'camera' shows them on each step of the presentation.\n        \n        The `id` attribute of the step element is used to identify it in the URL, but it's optional.\n        If it is not defined, it will get a default value of `step-N` where N is a number of slide.\n        This step is auto-assigned the id \"step-1\". You can also use `#step-1` in a link, to \n        point directly to this particular step.\n        \n        Positioning information is passed through data-* attributes.\n        \n        In the example below we only specify x and y position of the step element with `data-x=\"-1000\"`\n        and `data-y=\"-1500\"` attributes. This means that **the center** of the element (yes, the center)\n        will be positioned in point x = -1000px and y = -1500px of the presentation 'canvas'. It will not \n        be rotated or scaled.\n        \n        The \"step\" class is what *must* be used for every \"slide\". In this example we also use the \"slide\"\n        class, which adds a rectangle with some background and makes this presentation look like a traditional\n        powerpoint slide show. The \"slide\" class is entirely optional and indeed you wouldn't use it for\n        your cooler impress.js presentations.\n    -->\n    <div class=\"step slide title\" data-x=\"-2200\" data-y=\"-3000\">\n        <h1>Example Presentation: <br />\n            Classic Slides</h1>\n        <h2>Henrik Ingo</h2>\n        <h3>2015</h3>\n\n        <div class=\"notes\">\n        Any element with the class=\"notes\" will not be displayed. This can\n        be used for speaker notes. In fact, the impressConsole plugin will\n        show it in the speaker console!\n        </div>\n    </div>\n\n    <div id=\"toc\" class=\"step slide\" data-rel-x=\"2200\" data-rel-y=\"0\">\n        <h1>Table of Contents</h1>\n        <ul>\n            <li><a href=\"#step-1\">A title slide</a></li>\n            <li><a href=\"#step-2\">Table of Contents</a></li>\n            <li><a href=\"#step-3\">Text slide</a></li>\n            <li><a href=\"#step-4\">Bullet points</a></li>\n            <li><a href=\"#step-5\">Blockquote &amp; image</a></li>\n            <li><a href=\"#step-6\">More basic text styles</a></li>\n            <li><a href=\"#step-7\">Motion effects 101</a></li>\n            <li><a href=\"#addons\">Add-ons</a></li>\n            <li><a href=\"#moreinfo\">More info</a></li>\n        </ul>\n\n        <div class=\"notes\">\n           <p>Table of Contents, with links to other slides of this same presentation.</p>\n         \n           <p>Note that instead of absolute positioning we use relative positioning,\n           with the data-rel-x and data-rel-y attributes. This means the step is\n           positioned relative to the foregoing step. In other words, this is \n           equivalent to data-x=\"0\" data-y=\"-3000\".</p>\n        </div>\n    </div>\n\n    <div class=\"step slide\">\n        <h1>A slide with text</h1>\n        <p>This slide has a few paragraphs <br />(p element) of normal text.</p>\n        <p>Personally I like centered or even justified text, as it looks less boring. This can of course be set in <a href=\"css/classic-slides.css\">the css file</a>.</p>\n        <p>I really like the style on links in these presentations. I modified the border to be beveled, but it's mostly from <a href=\"http://impress.github.io/impress.js/\">@bartaz' original demo</a>. <a href=\"https://twitter.com/bartaz\">@bartaz</a> is the creator of impress.js.</p>\n\n        <div class=\"notes\">\n         In this slide, we don't even specify the relative position, rather\n         that too is inherited. So this slide will again be 1000px to the\n         right of the previous one.\n        </div>\n    </div>\n\n    <div class=\"step slide\">\n        <h1>Bullet points</h1>\n        <ul>\n            <li>A slide with bullet points. This is the first point.</li>\n            <li>Second point</li>\n            <li>Third point. Under this point we also have some sub-bullets:\n                <ul>\n                    <li>Sub-bullet 1</li>\n                    <li>Sub-bullet 2</li>\n                </ul>\n            </li>\n        </ul>\n\n        <div class=\"notes\">\n        </div>\n    </div>\n\n    <!--\n        This element introduces rotation.\n        \n        Notation shouldn't be a surprise. We use `data-rotate=\"30\"` attribute, meaning that this\n        element should be rotated by 30 degrees clockwise.        \n    -->\n    <div class=\"step slide\" data-rel-position=\"relative\" data-rel-x=\"2200\" data-rel-y=\"600\" data-rel-rotate-z=\"30\">\n        <h1>A blockquote &amp; image</h1>\n        <img src=\"images/3476636111_c551295ca4_b.jpg\" \n             alt=\"Mother Teresa holding a newborn baby\" \n             class=\"right\"/>\n        <blockquote>\n        Spread love everywhere you go. <br />Let no one ever come to you without leaving happier.\n        <p style=\"text-align: right\">Mother Teresa</p>\n        <p class=\"left bottom\"><small>Image credit: <a href=\"https://www.flickr.com/photos/peta-de-aztlan/3476636111/\">Peta_de_Aztlan</a>@Flickr. CC-BY 2.0</small></p>\n        </blockquote>\n\n        <div class=\"notes\">\n            We use <code>data-rel-position=\"relative\"</code> to make <code>data-rel-rotate-*</code> work,\n            and make <code>data-rel-x/data-rel-y/data-rel-y</code> be calculated at the coordination related to previous slide.\n            The relative position and rotation will be inherited by following slides,\n            so it's not necessary to repeat it again and again.\n        </div>\n    </div>\n\n    <div class=\"step slide\">\n        <h1>More text styles</h1>\n        <p>As usual, use <em>em</em> to emphasize, <br />\n           <strong>strong</strong> for strong, <u>u</u> for underline,<br />\n           <strike>strike</strike> for strikethrough and <q>q for inline quotations</q>.</p>\n           \n        <p>If you're a software engineer like me, you will often use the \n           <code>&lt;code&gt;</code> tag for monospaced inline text.</p>\n\n        <div class=\"notes\">\n        </div>\n    </div>\n\n    <div id=\"motions\" class=\"step slide\">\n        <h1>Motion effects 101</h1>\n        <p>Items on the slide can</p>\n        <p class=\"fly-in fly-out\">Fly in</p>\n        <p class=\"fade-in fade-out\" style=\"transition-delay: 2s\">Fade in</p>\n        <p class=\"zoom-in zoom-out\" style=\"transition-delay: 4s\">And zoom in</p>\n        \n        <p class=\"left bottom\"><small>...just like in PowerPoint. Yeah, I know I'm being lame, but it was fun to learn to do this in CSS3.</small></p>\n\n        <div class=\"notes\">\n        <p>This step here doesn't introduce anything new when it comes to data attributes, but you\n        should notice in the demo that some words of this text are being animated.\n        It's a very basic CSS transition that is applied to the elements when this step element is\n        reached.\n        </p><p>\n        At the very beginning of the presentation all step elements are given the class of `future`.\n        It means that they haven't been visited yet.\n        </p><p>        \n        When the presentation moves to given step `future` is changed to `present` class name.\n        That's how animation on this step works - text moves when the step has `present` class.\n        </p><p>\n        Finally when the step is left the `present` class is removed from the element and `past`\n        class is added.\n        </p><p>\n        So basically every step element has one of three classes: `future`, `present` and `past`.\n        Only one current step has the `present` class.\n        </p>\n        </div>\n    </div>\n\n    <div id=\"zoom\" class=\"step\" data-rel-reset data-rel-x=\"-0.25w\" data-rel-y=\"0.5h\" data-scale=\"0.5\">\n        <div class=\"notes\">\n            <p>This step zoom in to left bottom of previous slide to see to small text.</p>\n            <p>It's a empty and transparent.</p>\n            <p><code>data-rel-reset</code> is used to prevent this step from inheriting the relative positioning from previous slide.</p>\n        </div>\n    </div>\n\n    <div id=\"addons\" class=\"step slide title\" data-rel-to=\"motions\">\n        <h2>Add-ons</h2>\n        <div class=\"notes\">\n        <p>This version of impress.js includes several add-ons, striving to make this a\n           full featured presentation app.</p>\n        <p>The previous step breaks the slide flow, changes the relative position and rotation.</p>\n        <p>This slide use <code>data-rel-to</code> to inherit relative position and rotation from the slide before previous slide.</p>\n        </div>\n    </div>\n\n    <div class=\"step slide\" data-autoplay=\"3\">\n        <h1>Impress.js plugins</h1>\n        <ul>\n        <li>A new <a href=\"https://github.com/impress/impress.js/blob/master/src/plugins/README.md\">plugin framework</a> allows for rich extensibility,\n            without bloating the core rendering library.\n            <ul>\n            <li class=\"substep\">Press 'P' to open a presenter console.</li>\n            <li class=\"substep\">When you move the mouse, navigation controls are visible on your bottom right</li>\n            <li class=\"substep\">Autoplay makes the slides advance after a timeout</li>\n            <li class=\"substep\">Relative positioning plugin is often a more convenient way to position your slides when editing. (<a href=\"https://github.com/impress/impress.js/blob/master/examples/classic-slides/index.html\">See html for this presentation.</a>)</li>\n            </ul>\n        </li>\n        </ul>\n        <div class=\"notes\">\n        <p>This presentation also uses speaker notes. They are not visible in the presentation, but shown in the impress console.</p>\n        \n        <p>If you pressed P only now, this is the first time you see these notes. In fact, there has been notes on preceding slides as well.\n           You can use the navigation controls at the bottom of the impress console to browse back to them.</p>\n\n        <p>And did you notice how those bullet points appear one by one as you press space/arrow? That's another plugin, called substeps.</p>\n        </div>\n    </div>\n\n    <div class=\"step slide\">\n        <h1>Highlight.js</h1>\n        <pre><code>\n        // `init` API function that initializes (and runs) the presentation.\n        var init = function () {\n            if (initialized) { return; }\n            execPreInitPlugins();\n            \n            // First we set up the viewport for mobile devices.\n            // For some reason iPad goes nuts when it is not done properly.\n            var meta = $(\"meta[name='viewport']\") || document.createElement(\"meta\");\n            meta.content = \"width=device-width, minimum-scale=1, maximum-scale=1, user-scalable=no\";\n            if (meta.parentNode !== document.head) {\n                meta.name = 'viewport';\n                document.head.appendChild(meta);\n            }        \n        </code></pre>\n        <div class=\"notes\">\n        <p>The Highlight.js library provides really nice color coding of source code.\n           It automatically applies to any code inside a &lt;pre&gt;&lt;code&gt; element.</p>\n        <p>Highlight.js is found under the <a href=\"https://github.com/impress/impress.js/tree/master/extras\">extras/</a>\n           directory, since it is an independent third party plugin, not really an impress.js plugin. You have\n           to include it via it's own &lt;link&gt; and &lt;script&gt; tags.</p>\n        </div>\n    </div>\n\n    <div class=\"step slide\">\n        <h1>Mermaid.js</h1>\n        <div class=\"mermaid\">\n        %% This is a comment in mermaid markup\n        graph LR\n          A(Support for<br />diagrams)\n          B[Provided by<br />mermaid.js]\n          C{Already<br />know<br />mermaid?}\n          D(Tutorial)\n          E(Great, hope you enjoy!)\n          A-->B\n          B-->C\n          C--No-->D\n          C--Yes-->E\n          classDef startEnd fill:#fcc,stroke:#353,stroke-width:2px;\n          class A,D,E startEnd;\n        </div>\n\n        <h1><a href=\"http://docs.mathjax.org/en/latest/start.html\">MathJax.js</a></h1>\n        <p>Use \\(\\LaTeX\\), MathML or AsciiMath to properly show mathematical formula.</p>\n        <div class=\"notes\">\n          Mermaid.js, likewise in <a href=\"https://github.com/impress/impress.js/tree/master/extras\">extras/</a>\n           directory, draws SVG diagrams from a MarkDown-like syntax. To learn\n          more about it <a href=\"http://knsv.github.io/mermaid/index.html#usage\">read the fine manual</a>.\n        </div>\n    </div>\n\n    <div id=\"markdown\" class=\"step slide markdown\">\n# Markdown.js\n        \n* [Markdown.js](https://github.com/evilstreak/markdown-js) integration: for authors in a hurry!\n  * ...or lazy ;-)\n* Jot down bullet points in *Markdown*\n  * ...have it automatically converted to HTML\n  * Markdown is converted into a presentation client side, in the browser. This is unlike\n   existing tools like [Hovercraft](https://github.com/regebro/hovercraft) and \n   markdown-impress where you generate a new\n   html file on the command line.\n* [A more advanced Markdown presentation is here.](../markdown/)\n    </div>\n\n    <div id=\"acme\" class=\"step slide\">\n        <ul>\n        <li>Remember, in <em>impress.js</em> the full power of HTML5, CSS3 &amp; JavaScript is always at your fingertips!</li>\n        <li>For example, you can use tables, forms, or dynamic charts as you would on any web page:</li>\n        </ul>\n        <h2>Acme Inc Quarterly Profits</h2>\n\n        <!-- Improvised bar graph of divs, to avoid copying something like NVD3 into the repo. -->\n        <div id=\"acme-graph\">\n            <div id=\"acme-graph-bars\">\n                <div id=\"acme-graph-q1\"></div>\n                <div id=\"acme-graph-q2\"></div>\n                <div id=\"acme-graph-q3\"></div>\n                <div id=\"acme-graph-q4\"></div>\n            </div>\n            <div id=\"acme-graph-bottom\"></div>\n        </div>\n\n        <table border=\"1\">\n        <tr><td>Q1</td><td id=\"acme-q1\">234€</td></tr>\n        <tr><td>Q2</td><td id=\"acme-q2\">255€</td></tr>\n        <tr><td>Q3</td><td><input id=\"acme-q3\" size=\"5\" oninput=\"acmeDrawGraph();\" />€ <small>(insert here)</small></td></tr>\n        <tr><td>Q4</td><td><input id=\"acme-q4\" size=\"5\" oninput=\"acmeDrawGraph();\" />€</td></tr>\n        </table>\n        <div class=\"notes\">\n        </div>\n    </div>\n    <script type=\"text/javascript\">\n        var acmeDrawGraph = function() {\n            var profits = {};\n            \n            // Q1-Q2: get innerHTML, remove €\n            var value = document.getElementById('acme-q1').innerHTML;\n            if( value[value.length-1] == \"€\" ) value = value.substring(0, value.length-1);\n            profits['q1'] = value;\n            var value = document.getElementById('acme-q2').innerHTML;\n            if( value[value.length-1] == \"€\" ) value = value.substring(0, value.length-1);\n            profits['q2'] = value;\n            \n            // Q3-Q4: get input.value\n            profits['q3'] = document.getElementById('acme-q3').value;\n            profits['q4'] = document.getElementById('acme-q4').value;\n\n            // Convert all to numeric value, and remember max value for scaling purposes.\n            var max = profits['q1'];\n            for ( var q in profits ) {\n                profits[q] = isNaN(profits[q]) ? 0 : Number(profits[q]);\n                if( profits[q] > max ) {\n                    max = profits[q];\n                }\n            }\n            \n            // Draw the bar graph\n            for ( var q in profits ) {\n                var h = 300 * profits[q] / max;\n                var div = document.getElementById('acme-graph-'+q);\n                div.style = 'height: ' + h + 'px';\n            }\n        };\n        // This draws the first 2 bars during page load\n        acmeDrawGraph();\n    </script>\n\n    <!--\n        This step also sets a custom data-transition-duration. All of the above steps used the value set\n        in the root div#impress element, but it is also allowed to set it for each step. Since transitioning\n        to this step will rotate twice around it's axis, we give the transition a bit more time here.\n    -->\n    <div id=\"moreinfo\" class=\"step slide\" data-x=\"4400\" data-y=\"1200\" data-rotate=\"720\" data-transition-duration=\"2000\">\n        <h1>More info</h1>\n        <ul>\n        <li><a href=\"https://github.com/impress/impress.js/blob/master/DOCUMENTATION.md\">DOCUMENTATION.md</a> is the API reference.\n        </li>\n        <li><a href=\"https://github.com/impress/impress.js/blob/master/examples/classic-slides/index.html\">Source of this presentation itself</a> is commented</li>\n        <li><a href=\"http://impress.github.io/impress.js/\">Advanced Impress.js demo</a> by <a href=\"http://twitter.com/bartaz\">@bartaz</a>\n            <ul>\n            <li>Again, the <a href=\"http://github.com/impress/impress.js/blob/master/index.html\">html</a> \n                and <a href=\"https://github.com/impress/impress.js/blob/master/css/impress-demo.css\">css</a> source is well commented.</li>\n            </ul>\n        </li>\n        <li>More examples and demos:\n            <ul>\n            <li><a href=\"https://github.com/impress/impress.js/tree/master/examples\">examples/</a> in this repository</li>\n            <li><a href=\"https://github.com/impress/impress.js/wiki/Examples-and-demos\">on the impress.js wiki</a></li>\n            </ul>\n        </li>\n        <li>Check out <a href=\"https://github.com/impress/impressionist\">Impressionist</a>: a 3D GUI to create impress.js presentations</li>\n        </ul>\n\n        <div class=\"notes\">\n        </div>\n    </div>\n\n    <!-- This last, empty \"slide\" is set to be very large using the data-scale attribute, \n         so that it covers all the other slides you just saw. \n         It's a common way of zooming out at the end, to show the whole presentation.\n         In CSS, we set pointer-events:none to make this slide non-clickable. \n         It makes a difference at least for SVG content, such as the mermaid diagram. -->\n    <div id=\"overview\" class=\"step\" data-x=\"4500\" data-y=\"1500\" data-scale=\"10\" style=\"pointer-events: none;\">\n    </div>\n</div>\n\n<!--\n    Add navigation-ui controls: back, forward and a select list.\n    Add a progress indicator bar (current step / all steps)\n    Add the help popup plugin\n-->\n<div id=\"impress-toolbar\"></div>\n\n<div class=\"impress-progressbar\"><div></div></div>\n<div class=\"impress-progress\"></div>\n\n<div id=\"impress-help\"></div>\n\n<!-- Extra modules\n     Load highlight.js, mermaid.js, markdown.js and MathJax.js from extras.\n     If you're curious about details, these are initialized in src/plugins/extras/extras.js -->\n<script type=\"text/javascript\" src=\"../../extras/highlight/highlight.pack.js\"></script>\n<script type=\"text/javascript\" src=\"../../extras/mermaid/mermaid.min.js\"></script>\n<script type=\"text/javascript\" src=\"../../extras/markdown/markdown.js\"></script>\n<script type=\"text/javascript\" src=\"../../extras/mathjax/MathJax.js?config=TeX-AMS_CHTML\"></script>\n<!--\n    To make all described above really work, you need to include impress.js in the page.\n    You also need to call a `impress().init()` function to initialize impress.js presentation.\n    And you should do it in the end of your document. \n-->\n<script type=\"text/javascript\" src=\"../../js/impress.js\"></script>\n<script>impress().init();</script>\n\n</body>\n</html>\n"
  },
  {
    "path": "examples/cube/css/cube.css",
    "content": "@import url(fonts.css);\n\n/* Specific to this presentation */\n\ndiv.step p {\n    border: solid 2px black;\n    border-radius: 120px;\n    width: 170px;\n    height: 110px;\n    text-align: center;\n    padding-top: 65px;\n    background-color: black;\n    color: white;\n}\n\ndiv.step img {\n    border: solid 2px black;\n    border-radius: 120px;\n    width: 170px;\n    height: 170px;\n    text-align: center;\n    background-color: black;\n    color: white;\n}\n\n\n\n\n/*  Fallback message */\n\n.fallback-message {\n    font-family: sans-serif;\n    line-height: 1.3;\n\n    width: 780px;\n    padding: 10px 10px 0;\n    margin: 20px auto;\n\n    border: 1px solid #E4C652;\n    border-radius: 10px;\n    background: #EEDC94;\n}\n\n.fallback-message p {\n    margin-bottom: 10px;\n}\n\n.impress-supported .fallback-message {\n    display: none;\n}\n\n\n/* Body & steps */\nbody {\n    font-family: 'PT Sans', sans-serif;\n    min-height: 740px;\n\n    background: #00000f;\n    color: rgb(102, 102, 102);\n}\n\n.step {\n    position: relative;\n    width: 700px;\n    height: 700px;\n    padding: 40px 60px;\n    margin: 20px auto;\n\n    box-sizing:         border-box;\n\n    line-height: 1.5;\n\n    background-color: white;\n    border-radius: 10px;\n    box-shadow: 0 2px 6px rgba(0, 0, 0, .1);\n\n    text-shadow: 0 2px 2px rgba(0, 0, 0, .1);\n    font-family: 'Open Sans', Arial, sans-serif;\n    font-size: 30px;\n    letter-spacing: -1px;\n\n}\n\n#overview {\n    background-color: transparent;\n    border: none;\n    box-shadow: none;\n}\n\nh1, \nh2, \nh3 {\n    margin-bottom: 0.5em;\n    margin-top: 0.5em;\n    text-align: center;\n}\n\np {\n    margin: 0.7em;\n}\n\nli {\n    margin: 0.2em;    \n}\n\n/* Highlight.js used for coloring pre > code blocks. */\npre > code {\n    font-size: 14px;\n    text-shadow: 0 0 0 rgba(0, 0, 0, 0);\n}\n\n/* Inline code, no Highlight.js */\ncode {\n    font-family: \"Cutive mono\",\"Courier New\", monospace;\n}\n\n\na {\n    color: #ddd;\n    text-decoration: underline;\n}\na:hover,\na:focus {\n    background: rgba(200,200,200,1);\n    text-shadow: -1px 1px 2px rgba(100,100,100,0.5);\n    padding: 0 0.1em;\n    background: rgba(200,200,200,0.2);\n    border-radius: 0.2em;\n    border-bottom: 1px solid rgba(100,100,100,0.2);\n    border-left:   1px solid rgba(100,100,100,0.2);\n}\n\nblockquote {\n    font-family: 'PT Serif';\n    font-style: italic;\n    font-weight: 400;    \n}\n\nem {\n    text-shadow: 0 2px 2px rgba(0, 0, 0, .3);    \n}\n\nstrong {\n    text-shadow: -1px 1px 2px rgba(100,100,100,0.5);\n}\n\nq {\n    font-family: 'PT Serif';\n    font-style: italic;\n    font-weight: 400;    \n    text-shadow: 0 2px 2px rgba(0, 0, 0, .3);\n}\n\nstrike {\n    opacity: 0.7;\n}\n\nsmall {\n    font-size: 0.4em;\n}\n"
  },
  {
    "path": "examples/cube/css/fonts.css",
    "content": "/* latin-ext */\n@font-face {\n  font-family: 'Cutive Mono';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Cutive Mono'), local('CutiveMono-Regular'), url(http://fonts.gstatic.com/s/cutivemono/v4/N5odNRruTwjvCM8y77PhQSYE0-AqJ3nfInTTiDXDjU4.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'Cutive Mono';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Cutive Mono'), local('CutiveMono-Regular'), url(http://fonts.gstatic.com/s/cutivemono/v4/N5odNRruTwjvCM8y77PhQY4P5ICox8Kq3LLUNMylGO4.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/K88pR3goAWT7BTt32Z01mxJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/RjgO7rYTmqiVp7vzi-Q5URJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/LWCjsQkB6EMdfHrEVqA1KRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/xozscpT2726on7jbcb_pAhJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0370-03FF;\n}\n/* vietnamese */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/59ZRklaO5bWGqF5A9baEERJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/u-WUoqrET9fUeobQW7jkRRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/cJZKeOuBrn4kERxqtaUH3VtXRa8TVwTICgirnJhmVJw.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSq-j2U0lmluP9RWlSytm3ho.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSpX5f-9o1vgP2EXwfjgl7AY.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNShWV49_lSm1NYrwo-zkhivY.woff2) format('woff2');\n  unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSqaRobkAwv3vxw3jMhVENGA.woff2) format('woff2');\n  unicode-range: U+0370-03FF;\n}\n/* vietnamese */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSv8zf_FOSsgRmwsS7Aa9k2w.woff2) format('woff2');\n  unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSj0LW-43aMEzIO6XUTLjad8.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSugdm0LZdjqr5-oayXSOefg.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBjTOQ_MqJVwkKsUn0wKzc2I.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBjUj_cnvWIuuBMVgbX098Mw.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBkbcKLIaa1LC45dFaAfauRA.woff2) format('woff2');\n  unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBmo_sUJ8uO4YLWRInS22T3Y.woff2) format('woff2');\n  unicode-range: U+0370-03FF;\n}\n/* vietnamese */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBr6up8jxqWt8HVA3mDhkV_0.woff2) format('woff2');\n  unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBiYE0-AqJ3nfInTTiDXDjU4.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBo4P5ICox8Kq3LLUNMylGO4.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxmgpAmOCqD37_tyH_8Ri5MM.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxsPNMTLbnS9uQzHQlYieHUU.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxgyhumQnPMBCoGYhRaNxyyY.woff2) format('woff2');\n  unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxhUVAXEdVvYDDqrz3aeR0Yc.woff2) format('woff2');\n  unicode-range: U+0370-03FF;\n}\n/* vietnamese */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxlf4y_3s5bcYyyLIFUSWYUU.woff2) format('woff2');\n  unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxnywqdtBbUHn3VPgzuFrCy8.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxl2umOyRU7PgRiv8DXcgJjk.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Sans'), local('PTSans-Regular'), url(http://fonts.gstatic.com/s/ptsans/v8/JX7MlXqjSJNjQvI4heMMGvY6323mHUZFJMgTvxaG2iE.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Sans'), local('PTSans-Regular'), url(http://fonts.gstatic.com/s/ptsans/v8/vtwNVMP8y9C17vLvIBNZI_Y6323mHUZFJMgTvxaG2iE.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Sans'), local('PTSans-Regular'), url(http://fonts.gstatic.com/s/ptsans/v8/9kaD4V2pNPMMeUVBHayd7vY6323mHUZFJMgTvxaG2iE.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Sans'), local('PTSans-Regular'), url(http://fonts.gstatic.com/s/ptsans/v8/ATKpv8nLYAKUYexo8iqqrg.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Sans Bold'), local('PTSans-Bold'), url(http://fonts.gstatic.com/s/ptsans/v8/kTYfCWJhlldPf5LnG4ZnHCEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Sans Bold'), local('PTSans-Bold'), url(http://fonts.gstatic.com/s/ptsans/v8/g46X4VH_KHOWAAa-HpnGPiEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Sans Bold'), local('PTSans-Bold'), url(http://fonts.gstatic.com/s/ptsans/v8/hpORcvLZtemlH8gI-1S-7iEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Sans Bold'), local('PTSans-Bold'), url(http://fonts.gstatic.com/s/ptsans/v8/0XxGQsSc1g4rdRdjJKZrNPk_vArhqVIZ0nv9q090hN8.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Sans Italic'), local('PTSans-Italic'), url(http://fonts.gstatic.com/s/ptsans/v8/GpWpM_6S4VQLPNAQ3iWvVRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Sans Italic'), local('PTSans-Italic'), url(http://fonts.gstatic.com/s/ptsans/v8/7dSh6BcuqDLzS2qAASIeuhJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Sans Italic'), local('PTSans-Italic'), url(http://fonts.gstatic.com/s/ptsans/v8/DVKQJxMmC9WF_oplMzlQqRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Sans Italic'), local('PTSans-Italic'), url(http://fonts.gstatic.com/s/ptsans/v8/PIPMHY90P7jtyjpXuZ2cLFtXRa8TVwTICgirnJhmVJw.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Sans Bold Italic'), local('PTSans-BoldItalic'), url(http://fonts.gstatic.com/s/ptsans/v8/lILlYDvubYemzYzN7GbLkK-j2U0lmluP9RWlSytm3ho.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Sans Bold Italic'), local('PTSans-BoldItalic'), url(http://fonts.gstatic.com/s/ptsans/v8/lILlYDvubYemzYzN7GbLkJX5f-9o1vgP2EXwfjgl7AY.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Sans Bold Italic'), local('PTSans-BoldItalic'), url(http://fonts.gstatic.com/s/ptsans/v8/lILlYDvubYemzYzN7GbLkD0LW-43aMEzIO6XUTLjad8.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Sans Bold Italic'), local('PTSans-BoldItalic'), url(http://fonts.gstatic.com/s/ptsans/v8/lILlYDvubYemzYzN7GbLkOgdm0LZdjqr5-oayXSOefg.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Serif'), local('PTSerif-Regular'), url(http://fonts.gstatic.com/s/ptserif/v8/5hX15RUpPERmeybVlLQEWBTbgVql8nDJpwnrE27mub0.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Serif'), local('PTSerif-Regular'), url(http://fonts.gstatic.com/s/ptserif/v8/fU0HAfLiPHGlZhZpY6M7dBTbgVql8nDJpwnrE27mub0.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Serif'), local('PTSerif-Regular'), url(http://fonts.gstatic.com/s/ptserif/v8/CPRt--GVMETgA6YEaoGitxTbgVql8nDJpwnrE27mub0.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Serif'), local('PTSerif-Regular'), url(http://fonts.gstatic.com/s/ptserif/v8/I-OtoJZa3TeyH6D9oli3ifesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Serif Bold'), local('PTSerif-Bold'), url(http://fonts.gstatic.com/s/ptserif/v8/QABk9IxT-LFTJ_dQzv7xpDTOQ_MqJVwkKsUn0wKzc2I.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Serif Bold'), local('PTSerif-Bold'), url(http://fonts.gstatic.com/s/ptserif/v8/QABk9IxT-LFTJ_dQzv7xpDUj_cnvWIuuBMVgbX098Mw.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Serif Bold'), local('PTSerif-Bold'), url(http://fonts.gstatic.com/s/ptserif/v8/QABk9IxT-LFTJ_dQzv7xpCYE0-AqJ3nfInTTiDXDjU4.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Serif Bold'), local('PTSerif-Bold'), url(http://fonts.gstatic.com/s/ptserif/v8/QABk9IxT-LFTJ_dQzv7xpI4P5ICox8Kq3LLUNMylGO4.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Serif Italic'), local('PTSerif-Italic'), url(http://fonts.gstatic.com/s/ptserif/v8/O_WhD9hODL16N4KLHLX7xSEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Serif Italic'), local('PTSerif-Italic'), url(http://fonts.gstatic.com/s/ptserif/v8/3Nwg9VzlwLXPq3fNKwVRMCEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Serif Italic'), local('PTSerif-Italic'), url(http://fonts.gstatic.com/s/ptserif/v8/b31S45a_TNgaBApZhTgE6CEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Serif Italic'), local('PTSerif-Italic'), url(http://fonts.gstatic.com/s/ptserif/v8/03aPdn7fFF3H6ngCgAlQzPk_vArhqVIZ0nv9q090hN8.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Serif Bold Italic'), local('PTSerif-BoldItalic'), url(http://fonts.gstatic.com/s/ptserif/v8/Foydq9xJp--nfYIx2TBz9ede9INZm0R8ZMJUtfOsxrw.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Serif Bold Italic'), local('PTSerif-BoldItalic'), url(http://fonts.gstatic.com/s/ptserif/v8/Foydq9xJp--nfYIx2TBz9bpHcMS0zZe4mIYvDKG2oeM.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Serif Bold Italic'), local('PTSerif-BoldItalic'), url(http://fonts.gstatic.com/s/ptserif/v8/Foydq9xJp--nfYIx2TBz9RHJTnCUrjaAm2S9z52xC3Y.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Serif Bold Italic'), local('PTSerif-BoldItalic'), url(http://fonts.gstatic.com/s/ptserif/v8/Foydq9xJp--nfYIx2TBz9YWiMMZ7xLd792ULpGE4W_Y.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n\n"
  },
  {
    "path": "examples/cube/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\" />\n    <title>Cube | Explore impress.js in 3D | by Henrik Ingo @henrikingo</title>\n    <meta name=\"description\" content=\"Explore impress.js in 3D\" />\n    <meta name=\"author\" content=\"Henrik Ingo\" />\n    <link href=\"../../css/impress-common.css\" rel=\"stylesheet\" />\n    <link href=\"css/cube.css\" rel=\"stylesheet\" />\n</head>\n\n<body class=\"impress-not-supported\">\n<div class=\"fallback-message\">\n    <p>Your browser <b>doesn't support the features required</b> by impress.js, so you are presented with a simplified version of this presentation.</p>\n    <p>For the best experience please use the latest <b>Chrome</b>, <b>Safari</b> or <b>Firefox</b> browser.</p>\n</div>\n\n<div id=\"impress\" data-transition-duration=\"2000\">\n\n    <div class=\"step\" data-x=\"0\" data-y=\"0\" data-z=\"350\"\n         data-goto-key-list=\"ArrowUp ArrowDown ArrowRight ArrowLeft o\" data-goto-next-list=\"step-4 step-3 step-2 step-5 overview\">\n        <p style=\"position: absolute; top: 240px; left: 240px;\">Slides</p>\n    </div>\n\n    <div class=\"step\" data-x=\"350\" data-y=\"0\" data-z=\"0\" data-rotate-y=\"90\"\n         data-goto-key-list=\"ArrowUp ArrowDown ArrowRight ArrowLeft o\" data-goto-next-list=\"step-4 step-3 step-6 step-1 overview\">\n        <p style=\"position: absolute; top: 50px; left: 50px;\">In</p>\n        <p style=\"position: absolute; bottom: 50px; right: 50px;\">3D</p>\n    </div>\n\n    <div class=\"step\" data-x=\"0\" data-y=\"350\" data-z=\"0\" data-rotate-x=\"-90\" data-rotate-z=\"90\"\n         data-goto-key-list=\"ArrowUp ArrowDown ArrowRight ArrowLeft o\" data-goto-next-list=\"step-2 step-5 step-6 step-1 overview\">\n        <p style=\"position: absolute; top: 50px; left: 50px;\">Check</p>\n        <p style=\"position: absolute; top: 240px; left: 240px;\"><a href=\"https://impress.js.org\">impress.js</a></p>\n        <p style=\"position: absolute; bottom: 50px; right: 50px;\">out</p>\n    </div>\n\n    <div class=\"step\" data-x=\"0\" data-y=\"-350\" data-z=\"0\" data-rotate-x=\"90\" data-rotate-z=\"-90\"\n         data-goto-key-list=\"ArrowUp ArrowDown ArrowRight ArrowLeft o\" data-goto-next-list=\"step-5 step-2 step-6 step-1 overview\">\n        <p style=\"position: absolute; top: 50px; left: 50px;\">CSS</p>\n        <p style=\"position: absolute; top: 50px; right: 50px;\">Positioning</p>\n        <p style=\"position: absolute; bottom: 50px; left: 50px;\">is</p>\n        <p style=\"position: absolute; bottom: 50px; right: 50px;\">amazing</p>\n    </div>\n\n    <div class=\"step\" data-x=\"-350\" data-y=\"0\" data-z=\"0\" data-rotate-y=\"-90\" data-rotate-z=\"-180\"\n         data-goto-key-list=\"ArrowUp ArrowDown ArrowRight ArrowLeft o\" data-goto-next-list=\"step-3 step-4 step-6 step-1 overview\">\n        <p style=\"position: absolute; top: 50px; left: 50px;\">By</p>\n        <p style=\"position: absolute; top: 50px; right: 50px;\">hingo</p>\n        <img src=\"img/Henrik_mustache.jpg\" alt=\"Henrik Ingo portrait, with mustache\" style=\"position: absolute; top: 275px; left: 275px;\">\n        <p style=\"position: absolute; bottom: 50px; left: 50px;\">bartaz</p>\n        <p style=\"position: absolute; bottom: 50px; right: 50px; padding-top: 30px; height: 140px;\"><a href=\"https://github.com/impress/impress.js/graphs/contributors\">+70 contributors</a></p>\n    </div>\n\n    <div class=\"step\" data-x=\"0\" data-y=\"0\" data-z=\"-350\" data-rotate-y=\"-180\" data-rotate-z=\"-180\"\n         data-goto-key-list=\"ArrowUp ArrowDown ArrowRight ArrowLeft o\" data-goto-next-list=\"step-3 step-4 step-2 step-5 overview\">\n        <p style=\"position: absolute; top: 50px; left: 50px;\">Open</p>\n        <p style=\"position: absolute; top: 50px; right: 50px;\">Source</p>\n        <p style=\"position: absolute; top: 240px; left: 50px;\">&amp;</p>\n        <p style=\"position: absolute; top: 240px; right: 50px;\">HTML</p>\n        <p style=\"position: absolute; bottom: 50px; left: 50px;\">CSS</p>\n        <p style=\"position: absolute; bottom: 50px; right: 50px;\">JavaScript</p>\n    </div>\n\n    <div id=\"overview\" class=\"step\" data-x=\"700\" data-y=\"-100\" data-z=\"1000\" data-scale=\"1\" style=\"pointer-events: none;\">\n    </div>\n</div>\n\n<div id=\"impress-toolbar\"></div>\n<div id=\"impress-help\"></div>\n\n<script type=\"text/javascript\" src=\"../../js/impress.js\"></script>\n<script>impress().init();</script>\n\n</body>\n</html>\n"
  },
  {
    "path": "examples/index.html",
    "content": "<html>\n<head>\n<title>Example presentations</title>\n</head>\n<body><h1>Example presentations</h1>\n<ul><br />\n  <li><a href=\"2D-navigation/\">2D-navigation</a></li>\n  <li><a href=\"3D-positions/\">3D-positions</a></li>\n  <li><a href=\"3D-rotations/\">3D-rotations</a></li>\n  <li><a href=\"classic-slides/\">classic-slides</a></li>\n  <li><a href=\"cube/\">cube</a></li>\n  <li><a href=\"markdown/\">markdown</a></li>\n</ul>\n</body>\n</html>"
  },
  {
    "path": "examples/markdown/css/devopsy.css",
    "content": "/***** Menu where this style is selected *****/\n.devopsy .css-menu-devopsy {\n    border: solid 1px #aaaaaa;\n}\n\n\n/***** Presentation *****/\n\nbody.devopsy {\n    background: #000;\n}\n\n.devopsy .step {\n    color: #ffff00;\n}\n\n/***** Styles *****/\n.devopsy h1, \n.devopsy h2, \n.devopsy h3 {\n    color: #ff6600;\n}\n\n.devopsy a {\n    color: #00ffff;\n}\n\n.devopsy blockquote {\n    text-shadow: 3px 3px 3px rgba(255, 100, 0, .7);    \n}\n\n.devopsy em {\n    text-shadow: 3px 3px 3px rgba(255, 100, 0, .7);    \n}\n\n.devopsy strong {\n    text-shadow: 3px 3px 3px rgba(255, 100, 0, .7);    \n}\n\n.devopsy q {\n    text-shadow: 3px 3px 3px rgba(255, 100, 0, .7);    \n}\n\n.devopsy strike {\n    opacity: 0.7;\n}\n\n"
  },
  {
    "path": "examples/markdown/css/effects.css",
    "content": "/***** Menu where this style is selected *****/\n.effects .css-menu-effects {\n    border: solid 1px #aaaaaa;\n}\n\n/***** Presentation *****/\n\nbody.effects {\n    font-family: 'PT Sans', sans-serif;\n    min-height: 740px;\n\n    background: #dde5dd;\n}\n\n.effects .step {\n}\n\n\n/* Color effect. */\n.effects .past > *,\n.effects .future > * {\n    color: inherit;\n    transition: 3s;\n}\n.effects .present > * {\n    color: rgb(200, 102, 102);\n    transition: 3s;\n}\n\n/* Fly right */\n\n.effects .past h1,\n.effects .future h1 {\n    transform:        scale(3);\n    transition: 1s;\n}\n.effects .present h1 {\n    transform:        scale(1);\n    transition: 1s;\n}\n\n"
  },
  {
    "path": "examples/markdown/css/fonts.css",
    "content": "/* latin-ext */\n@font-face {\n  font-family: 'Cutive Mono';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Cutive Mono'), local('CutiveMono-Regular'), url(http://fonts.gstatic.com/s/cutivemono/v4/N5odNRruTwjvCM8y77PhQSYE0-AqJ3nfInTTiDXDjU4.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'Cutive Mono';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Cutive Mono'), local('CutiveMono-Regular'), url(http://fonts.gstatic.com/s/cutivemono/v4/N5odNRruTwjvCM8y77PhQY4P5ICox8Kq3LLUNMylGO4.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/K88pR3goAWT7BTt32Z01mxJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/RjgO7rYTmqiVp7vzi-Q5URJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/LWCjsQkB6EMdfHrEVqA1KRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/xozscpT2726on7jbcb_pAhJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0370-03FF;\n}\n/* vietnamese */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/59ZRklaO5bWGqF5A9baEERJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/u-WUoqrET9fUeobQW7jkRRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('Open Sans'), local('OpenSans'), url(http://fonts.gstatic.com/s/opensans/v13/cJZKeOuBrn4kERxqtaUH3VtXRa8TVwTICgirnJhmVJw.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSq-j2U0lmluP9RWlSytm3ho.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSpX5f-9o1vgP2EXwfjgl7AY.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNShWV49_lSm1NYrwo-zkhivY.woff2) format('woff2');\n  unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSqaRobkAwv3vxw3jMhVENGA.woff2) format('woff2');\n  unicode-range: U+0370-03FF;\n}\n/* vietnamese */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSv8zf_FOSsgRmwsS7Aa9k2w.woff2) format('woff2');\n  unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSj0LW-43aMEzIO6XUTLjad8.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: normal;\n  font-weight: 600;\n  src: local('Open Sans Semibold'), local('OpenSans-Semibold'), url(http://fonts.gstatic.com/s/opensans/v13/MTP_ySUJH_bn48VBG8sNSugdm0LZdjqr5-oayXSOefg.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBjTOQ_MqJVwkKsUn0wKzc2I.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBjUj_cnvWIuuBMVgbX098Mw.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBkbcKLIaa1LC45dFaAfauRA.woff2) format('woff2');\n  unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBmo_sUJ8uO4YLWRInS22T3Y.woff2) format('woff2');\n  unicode-range: U+0370-03FF;\n}\n/* vietnamese */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBr6up8jxqWt8HVA3mDhkV_0.woff2) format('woff2');\n  unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBiYE0-AqJ3nfInTTiDXDjU4.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('Open Sans Italic'), local('OpenSans-Italic'), url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBo4P5ICox8Kq3LLUNMylGO4.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxmgpAmOCqD37_tyH_8Ri5MM.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxsPNMTLbnS9uQzHQlYieHUU.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxgyhumQnPMBCoGYhRaNxyyY.woff2) format('woff2');\n  unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxhUVAXEdVvYDDqrz3aeR0Yc.woff2) format('woff2');\n  unicode-range: U+0370-03FF;\n}\n/* vietnamese */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxlf4y_3s5bcYyyLIFUSWYUU.woff2) format('woff2');\n  unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxnywqdtBbUHn3VPgzuFrCy8.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'Open Sans';\n  font-style: italic;\n  font-weight: 600;\n  src: local('Open Sans Semibold Italic'), local('OpenSans-SemiboldItalic'), url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxl2umOyRU7PgRiv8DXcgJjk.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Sans'), local('PTSans-Regular'), url(http://fonts.gstatic.com/s/ptsans/v8/JX7MlXqjSJNjQvI4heMMGvY6323mHUZFJMgTvxaG2iE.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Sans'), local('PTSans-Regular'), url(http://fonts.gstatic.com/s/ptsans/v8/vtwNVMP8y9C17vLvIBNZI_Y6323mHUZFJMgTvxaG2iE.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Sans'), local('PTSans-Regular'), url(http://fonts.gstatic.com/s/ptsans/v8/9kaD4V2pNPMMeUVBHayd7vY6323mHUZFJMgTvxaG2iE.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Sans'), local('PTSans-Regular'), url(http://fonts.gstatic.com/s/ptsans/v8/ATKpv8nLYAKUYexo8iqqrg.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Sans Bold'), local('PTSans-Bold'), url(http://fonts.gstatic.com/s/ptsans/v8/kTYfCWJhlldPf5LnG4ZnHCEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Sans Bold'), local('PTSans-Bold'), url(http://fonts.gstatic.com/s/ptsans/v8/g46X4VH_KHOWAAa-HpnGPiEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Sans Bold'), local('PTSans-Bold'), url(http://fonts.gstatic.com/s/ptsans/v8/hpORcvLZtemlH8gI-1S-7iEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Sans Bold'), local('PTSans-Bold'), url(http://fonts.gstatic.com/s/ptsans/v8/0XxGQsSc1g4rdRdjJKZrNPk_vArhqVIZ0nv9q090hN8.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Sans Italic'), local('PTSans-Italic'), url(http://fonts.gstatic.com/s/ptsans/v8/GpWpM_6S4VQLPNAQ3iWvVRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Sans Italic'), local('PTSans-Italic'), url(http://fonts.gstatic.com/s/ptsans/v8/7dSh6BcuqDLzS2qAASIeuhJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Sans Italic'), local('PTSans-Italic'), url(http://fonts.gstatic.com/s/ptsans/v8/DVKQJxMmC9WF_oplMzlQqRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Sans Italic'), local('PTSans-Italic'), url(http://fonts.gstatic.com/s/ptsans/v8/PIPMHY90P7jtyjpXuZ2cLFtXRa8TVwTICgirnJhmVJw.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Sans Bold Italic'), local('PTSans-BoldItalic'), url(http://fonts.gstatic.com/s/ptsans/v8/lILlYDvubYemzYzN7GbLkK-j2U0lmluP9RWlSytm3ho.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Sans Bold Italic'), local('PTSans-BoldItalic'), url(http://fonts.gstatic.com/s/ptsans/v8/lILlYDvubYemzYzN7GbLkJX5f-9o1vgP2EXwfjgl7AY.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Sans Bold Italic'), local('PTSans-BoldItalic'), url(http://fonts.gstatic.com/s/ptsans/v8/lILlYDvubYemzYzN7GbLkD0LW-43aMEzIO6XUTLjad8.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Sans';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Sans Bold Italic'), local('PTSans-BoldItalic'), url(http://fonts.gstatic.com/s/ptsans/v8/lILlYDvubYemzYzN7GbLkOgdm0LZdjqr5-oayXSOefg.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Serif'), local('PTSerif-Regular'), url(http://fonts.gstatic.com/s/ptserif/v8/5hX15RUpPERmeybVlLQEWBTbgVql8nDJpwnrE27mub0.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Serif'), local('PTSerif-Regular'), url(http://fonts.gstatic.com/s/ptserif/v8/fU0HAfLiPHGlZhZpY6M7dBTbgVql8nDJpwnrE27mub0.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Serif'), local('PTSerif-Regular'), url(http://fonts.gstatic.com/s/ptserif/v8/CPRt--GVMETgA6YEaoGitxTbgVql8nDJpwnrE27mub0.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 400;\n  src: local('PT Serif'), local('PTSerif-Regular'), url(http://fonts.gstatic.com/s/ptserif/v8/I-OtoJZa3TeyH6D9oli3ifesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Serif Bold'), local('PTSerif-Bold'), url(http://fonts.gstatic.com/s/ptserif/v8/QABk9IxT-LFTJ_dQzv7xpDTOQ_MqJVwkKsUn0wKzc2I.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Serif Bold'), local('PTSerif-Bold'), url(http://fonts.gstatic.com/s/ptserif/v8/QABk9IxT-LFTJ_dQzv7xpDUj_cnvWIuuBMVgbX098Mw.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Serif Bold'), local('PTSerif-Bold'), url(http://fonts.gstatic.com/s/ptserif/v8/QABk9IxT-LFTJ_dQzv7xpCYE0-AqJ3nfInTTiDXDjU4.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: normal;\n  font-weight: 700;\n  src: local('PT Serif Bold'), local('PTSerif-Bold'), url(http://fonts.gstatic.com/s/ptserif/v8/QABk9IxT-LFTJ_dQzv7xpI4P5ICox8Kq3LLUNMylGO4.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Serif Italic'), local('PTSerif-Italic'), url(http://fonts.gstatic.com/s/ptserif/v8/O_WhD9hODL16N4KLHLX7xSEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Serif Italic'), local('PTSerif-Italic'), url(http://fonts.gstatic.com/s/ptserif/v8/3Nwg9VzlwLXPq3fNKwVRMCEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Serif Italic'), local('PTSerif-Italic'), url(http://fonts.gstatic.com/s/ptserif/v8/b31S45a_TNgaBApZhTgE6CEAvth_LlrfE80CYdSH47w.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 400;\n  src: local('PT Serif Italic'), local('PTSerif-Italic'), url(http://fonts.gstatic.com/s/ptserif/v8/03aPdn7fFF3H6ngCgAlQzPk_vArhqVIZ0nv9q090hN8.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n/* cyrillic-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Serif Bold Italic'), local('PTSerif-BoldItalic'), url(http://fonts.gstatic.com/s/ptserif/v8/Foydq9xJp--nfYIx2TBz9ede9INZm0R8ZMJUtfOsxrw.woff2) format('woff2');\n  unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;\n}\n/* cyrillic */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Serif Bold Italic'), local('PTSerif-BoldItalic'), url(http://fonts.gstatic.com/s/ptserif/v8/Foydq9xJp--nfYIx2TBz9bpHcMS0zZe4mIYvDKG2oeM.woff2) format('woff2');\n  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* latin-ext */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Serif Bold Italic'), local('PTSerif-BoldItalic'), url(http://fonts.gstatic.com/s/ptserif/v8/Foydq9xJp--nfYIx2TBz9RHJTnCUrjaAm2S9z52xC3Y.woff2) format('woff2');\n  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n  font-family: 'PT Serif';\n  font-style: italic;\n  font-weight: 700;\n  src: local('PT Serif Bold Italic'), local('PTSerif-BoldItalic'), url(http://fonts.gstatic.com/s/ptserif/v8/Foydq9xJp--nfYIx2TBz9YWiMMZ7xLd792ULpGE4W_Y.woff2) format('woff2');\n  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;\n}\n\n"
  },
  {
    "path": "examples/markdown/css/markdown-slides.css",
    "content": "/***** Fonts *****/\n\n/* @import url(http://fonts.googleapis.com/css?family=Open+Sans:regular,semibold,italic,italicsemibold|PT+Sans:400,700,400italic,700italic|PT+Serif:400,700,400italic,700italic|Cutive+Mono); */\n@import url(fonts.css);\n\n\n/***** Fallback message *****/\n\n.fallback-message {\n    font-family: sans-serif;\n    line-height: 1.3;\n\n    width: 780px;\n    padding: 10px 10px 0;\n    margin: 20px auto;\n\n    border: 1px solid #E4C652;\n    border-radius: 10px;\n    background: #EEDC94;\n}\n\n.fallback-message p {\n    margin-bottom: 10px;\n}\n\n.impress-supported .fallback-message {\n    display: none;\n}\n\n/***** Per slide hacks *****/\n\ndiv#step-4 > p:last-child {\n    margin-top: 150px;\n    font-size: 0.5em;\n    text-align: right;\n}\n\ndiv#tilted-slide {\n    text-shadow: 20px 20px 10px rgba(0, 0, 0, .4);\n}\ndiv#tilted-slide h1 {\n    margin-top: 200px;\n}\n\n/***** Menu where this style is selected *****/\n.css-menu-bw {\n    border: solid 1px rgb(102, 102, 102);\n}\n\n.effects .css-menu-bw,\n.devopsy .css-menu-bw {\n    border: none;\n}\n\n/***** Presentation *****/\n\nbody {\n    font-family: 'PT Sans', sans-serif;\n    min-height: 740px;\n\n    background: #fff;\n}\n\n.step {\n    position: relative;\n    width: 900px;\n    height: 700px;\n    padding: 40px 60px;\n    margin: 20px auto;\n\n    box-sizing:         border-box;\n\n    color: rgb(102, 102, 102);\n    text-shadow: 0 2px 2px rgba(0, 0, 0, .1);\n    font-family: 'Open Sans', Arial, sans-serif;\n    font-size: 30px;\n    letter-spacing: -1px;\n    line-height: 1.5;\n}\n\n/*\n    Make inactive steps a little bit transparent.\n*/\n.impress-enabled .step {\n    margin: 0;\n    opacity: 0.3;\n    transition:         opacity 1s;\n}\n.impress-enabled .step.active { opacity: 1 }\n\nh1, \nh2, \nh3 {\n    margin-bottom: 0.5em;\n    margin-top: 0.5em;\n    text-align: center;\n}\n\np {\n    margin: 0.7em;\n}\n\nli {\n    margin: 0.2em;    \n}\n\n/* Highlight.js used for coloring pre > code blocks. */\npre > code {\n    font-size: 14px;\n    text-shadow: 0 0 0 rgba(0, 0, 0, 0);\n}\n\n/* Inline code, no Highlight.js */\ncode {\n    font-family: \"Cutive mono\",\"Courier New\", monospace;\n}\n\n\na {\n    color: inherit;\n    text-decoration: none;\n    padding: 0 0.1em;\n    text-shadow: -1px 1px 2px rgba(100,100,100,0.9);\n    border-radius: 0.2em;\n\n    transition:         0.5s;\n}\na:hover,\na:focus {\n    text-shadow: -1px 1px 2px rgba(100,100,100,0.5);\n}\n\nblockquote {\n    font-family: 'PT Serif';\n    font-style: italic;\n    font-weight: 400;    \n}\n\nem {\n    text-shadow: 0 2px 2px rgba(0, 0, 0, .3);    \n}\n\nstrong {\n    text-shadow: -1px 1px 2px rgba(100,100,100,0.5);\n}\n\nq {\n    font-family: 'PT Serif';\n    font-style: italic;\n    font-weight: 400;    \n    text-shadow: 0 2px 2px rgba(0, 0, 0, .3);\n}\n\nstrike {\n    opacity: 0.7;\n}\n\nimg {\n    width: 300px;\n    float: right;\n    margin-left:   40px;\n    margin-right:   0px;\n    margin-top:    20px;\n    margin-bottom: 20px;    \n}\n\n\n/* \n    Specific styles for: .title steps\n*/\n\n.title h1,\n.title h2,\n.title h3 {\n    position: absolute;\n    left: 45px; /* slide width is 900px, so this is like a 5% margin on both sides */\n    width: 90%;\n    text-align: center;    \n}\n.title h1 { top: 50px; }\n.title h2 { top: 400px; }\n.title h3 { top: 500px; }"
  },
  {
    "path": "examples/markdown/index.html",
    "content": "<!doctype html>\n\n<!--\n  A presentation done entirely in Markdown, as found in extras/markdown/.\n  \n  By: @henrikingo\n-->\n\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\" />\n    <title>Markdown in impress.js | by Henrik Ingo @henrikingo</title>\n    \n    <meta name=\"description\" content=\"Authoring impress.js presentations in Markdown\" />\n    <meta name=\"author\" content=\"Henrik Ingo\" />\n    <link rel=\"stylesheet\" href=\"../../extras/highlight/styles/github.css\">\n\n    <!-- Common styles for example presentations -->  \n    <link href=\"../../css/impress-common.css\" rel=\"stylesheet\" />\n    <!--\n        Styles specific for this example presentation. \n    -->\n    <link href=\"css/markdown-slides.css\" rel=\"stylesheet\" />\n    <link href=\"css/devopsy.css\" rel=\"stylesheet\" />\n    <link href=\"css/effects.css\" rel=\"stylesheet\" />\n    \n</head>\n\n<body class=\"impress-not-supported\">\n<div class=\"fallback-message\">\n    <p>Your browser <b>doesn't support the features required</b> by impress.js, so you are presented with a simplified version of this presentation.</p>\n    <p>For the best experience please use the latest <b>Chrome</b>, <b>Safari</b> or <b>Firefox</b> browser.</p>\n</div>\n\n<div id=\"impress\" data-transition-duration=\"1000\">\n\n<div id=\"markdown\" class=\"step slide markdown\" data-rel-x=\"0\" data-rel-y=\"900\">\n# Markdown \n## to author Impress.js presentations\n\n* This presentation was written entirely in Markdown\n* Added by popular request\n  * Easy way to make quick, simple yet aesthetic, presentations\n  * Authoring without all the clutter of HTML\n\n-----\n# Markdown.js\n\n* Provided by [Markdown.js](https://github.com/evilstreak/markdown-js) in [extras/](https://github.com/impress/impress.js/tree/master/extras)\n* Jot down your bullet points in *Markdown* & have it automatically converted to HTML\n* Note: The Markdown is converted into a presentation client side, in the browser. This is unlike\n  existing tools like [Hovercraft](https://github.com/regebro/hovercraft) and \n  markdown-impress where you generate a new\n  html file on the command line.\n* This combines the ease of typing Markdown with the full power of impress.js HTML5+CSS3+JavaScript!\n\n-----\n# Styles\n\n* You can use *italics* & **bold**\n* ...and `code`\n\n\n-----\n# A blockquote &amp; image\n\n![Mother Teresa holding a newborn baby](images/3476636111_c551295ca4_b.jpg)\n\n> Spread love everywhere you go.\n> Let no one ever come to you without leaving happier.\n\n*-- Mother Teresa*\n\nImage credit: [Peta de Aztlan](https://www.flickr.com/photos/peta-de-aztlan/3476636111/)@Flickr. CC-BY 2.0\n\n-----\n# Code\n\nWhen also using [Highlight.js](https://highlightjs.org/) integration, code blocks in Markdown\nare converted to HTML first, then colored by Highlight.js:\n\n        // `init` API function that initializes (and runs) the presentation.\n        var init = function () {\n            if (initialized) { return; }\n            execPreInitPlugins();\n            \n            // First we set up the viewport for mobile devices.\n            // For some reason iPad goes nuts when it is not done properly.\n            var meta = $(\"meta[name='viewport']\") || document.createElement(\"meta\");\n            meta.content = \"width=device-width, minimum-scale=1, maximum-scale=1, user-scalable=no\";\n            if (meta.parentNode !== document.head) {\n                meta.name = 'viewport';\n                document.head.appendChild(meta);\n            }        \n\n-----\n\n# Slide separator\n\n* 5 dashes are used to separate slides:\n\n`-----`\n\n* Attributes from `div.step` element are simply repeated.\n  * Better use relative positioning, lest your slides will all be on top of each other.\n* If you need to set some attributes, just use HTML and create a div element, then write \n  Markdown inside each div.\n</div>\n\n<div id=\"title-slide\" class=\"step slide title markdown\" data-x=\"2000\" data-y=\"5000\">\n# Title slide\n## This slide has different CSS class than the previous ones\n</div>\n\n\n<div id=\"tilted-slide\" class=\"step slide markdown\" data-rotate=\"-85\" data-x=\"2000\" data-y=\"2000\" data-z=\"1000\" data-scale=\"4\">\n# Simplicity of Markdown & Full power of Impress.js\n  \n* This slide sets its own `rotate`, `x`, `y`, and even `z` coordinates\n* Note that the Mother Teresa slide earlier was pimped with some custom CSS, all the while\n  the content was written in simple Markdown.\n</div>\n\n<div class=\"step slide title markdown\" data-rotate=\"-85\" data-x=\"4000\" data-y=\"2000\" data-markdown-dialect=\"Maruku\">\nThis is an example of a slide with a specific Markdown dialect.\n===============================================================\n</div>\n\n<script type=\"text/javascript\">\nvar enableBwCss = function(){\n    disableDevopsCss();\n    disableEffectsCss();\n};\n\nvar enableDevopsCss = function(){\n    document.body.classList.add(\"devopsy\");\n    disableEffectsCss();\n};\n\nvar disableDevopsCss = function(){\n    document.body.classList.remove(\"devopsy\");\n};\n\nvar enableEffectsCss = function(){\n    document.body.classList.add(\"effects\");\n    disableDevopsCss();\n};\n\nvar disableEffectsCss = function(){\n    document.body.classList.remove(\"effects\");\n};\n</script>\n\n\n<div id=\"js-slide\" class=\"step slide\" data-rotate=\"0\" data-x=\"4000\" data-y=\"5000\">\n<h1>CSS &amp; JavaScript magic</h1>\n\n<p>Just to emphasize my point, this last slide allows you to use a JavaScript powered menu to\ntoggle the CSS style:</p>\n\n<p><a href=\"#\" onclick=\"enableBwCss();\" class=\"css-menu-bw\">Black &amp; white</a>, \n<a href=\"#\" onclick=\"enableDevopsCss();\" class=\"css-menu-devopsy\">Devopsy</a>, \n<a href=\"#\" onclick=\"enableEffectsCss();\" class=\"css-menu-effects\">Effects overload</a></p>\n\n<p>Simplicity of Markdown married with full power of Impress.js!</p>\n\n</div>\n\n\n<div id=\"overview\" class=\"step\" data-x=\"5000\" data-y=\"4000\" data-scale=\"10\" style=\"pointer-events: none;\" data-rotate=\"5\">\n</div>\n\n</div>\n\n<div id=\"impress-toolbar\"></div>\n<div id=\"impress-help\"></div>\n\n\n<!-- Extra modules\n     Load highlight.js, mermaid.js and markdown.js from extras.\n     See also src/plugins/extras/extras.js -->\n<script type=\"text/javascript\" src=\"../../extras/highlight/highlight.pack.js\"></script>\n<script type=\"text/javascript\" src=\"../../extras/mermaid/mermaid.min.js\"></script>\n<script type=\"text/javascript\" src=\"../../extras/markdown/markdown.js\"></script>\n<!-- <script src=\"https://cdn.jsdelivr.net/npm/marked/marked.min.js\"></script> -->\n\n<!--\n    To make all described above really work, you need to include impress.js in the page.\n    You also need to call a `impress().init()` function to initialize impress.js presentation.\n    And you should do it in the end of your document. \n-->\n<script type=\"text/javascript\" src=\"../../js/impress.js\"></script>\n<script>impress().init();</script>\n</body>\n</html>\n"
  },
  {
    "path": "index.html",
    "content": "<!doctype html>\n\n<!--\n\n    Welcome to the light side of the source, young padawan.\n\n    One step closer to learn something interesting you are...\n\n                               ____                  \n                            _.' :  `._               \n                        .-.'`.  ;   .'`.-.           \n               __      / : ___\\ ;  /___ ; \\      __  \n             ,'_ \"\"=-.:__;\".-.\";: :\".-.\":__;.-=\"\" _`,\n             :' `.t\"\"=-.. '<@.`;_  ',@:` ..-=\"\"j.' `;\n                  `:-.._J '-.-'L__ `-.-' L_..-;'     \n                    \"-.__ ;  .-\"  \"-.  : __.-\"       \n                        L ' /.======.\\ ' J           \n                         \"-.   \"__\"   .-\"            \n                        __.l\"-:_JL_;-\";.__           \n                     .-j/'.;  ;\"\"\"\"  / .'\\\"-.        \n                   .' /:`. \"-.:     .-\" .';  `.      \n                .-\"  / ;  \"-. \"-..-\" .-\"  :    \"-.   \n             .+\"-.  : :      \"-.__.-\"      ;-._   \\  \n             ; \\  `.; ;                    : : \"+. ; \n             :  ;   ; ;                    : ;  : \\: \n             ;  :   ; :                    ;:   ;  : \n            : \\  ;  :  ;                  : ;  /  :: \n            ;  ; :   ; :                  ;   :   ;: \n            :  :  ;  :  ;                : :  ;  : ; \n            ;\\    :   ; :                ; ;     ; ; \n            : `.\"-;   :  ;              :  ;    /  ; \n             ;    -:   ; :              ;  : .-\"   : \n             :\\     \\  :  ;            : \\.-\"      : \n              ;`.    \\  ; :            ;.'_..-=  / ; \n              :  \"-.  \"-:  ;          :/.\"      .'  :\n               \\         \\ :          ;/  __        :\n                \\       .-`.\\        /t-\"\"  \":-+.   :\n                 `.  .-\"    `l    __/ /`. :  ; ; \\  ;\n                   \\   .-\" .-\"-.-\"  .' .'j \\  /   ;/ \n                    \\ / .-\"   /.     .'.' ;_:'    ;  \n                     :-\"\"-.`./-.'     /    `.___.'   \n                           \\ `t  ._  /               \n                            \"-.t-._:'                \n\n-->\n\n<!--\n    \n    So you'd like to know how to use impress.js?\n    \n    You've made the first, very important step - you're reading the source code.\n    And that's how impress.js presentations are built - with HTML and CSS code.\n    \n    Believe me, you need quite decent HTML and CSS skills to be able to use impress.js effectively.\n    More importantly, you need to be a designer. There are no default styles or layouts for impress.js presentations.\n    \n    You need to design and build it by hand.\n    \n    So...\n    \n    Would you still like to know how to use impress.js?\n    \n-->\n\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=1024\" />\n    <meta name=\"apple-mobile-web-app-capable\" content=\"yes\" />\n    <title>impress.js | presentation tool based on the power of CSS3 transforms and transitions in modern browsers | by Bartek Szopka @bartaz</title>\n    \n    <meta name=\"description\" content=\"impress.js is a presentation tool based on the power of CSS3 transforms and transitions in modern browsers and inspired by the idea behind prezi.com.\" />\n    <meta name=\"author\" content=\"Bartek Szopka\" />\n\n    <link href=\"//fonts.googleapis.com/css?family=Open+Sans:regular,semibold,italic,italicsemibold|PT+Sans:400,700,400italic,700italic|PT+Serif:400,700,400italic,700italic\" rel=\"stylesheet\" />\n\n    <!--\n        \n        Impress.js doesn't depend on any external stylesheets. It adds all of the styles it needs for the\n        presentation to work.\n        \n        This style below contains styles only for demo presentation. Browse it to see how impress.js\n        classes are used to style presentation steps, or how to apply fallback styles, but I don't want\n        you to use them directly in your presentation.\n        \n        Be creative, build your own. We don't really want all impress.js presentations to look the same,\n        do we?\n        \n        When creating your own presentation get rid of this file. Start from scratch, it's fun!\n        \n    -->\n    <link href=\"css/impress-demo.css\" rel=\"stylesheet\" />\n    <link href=\"css/impress-common.css\" rel=\"stylesheet\" />\n    \n    <link rel=\"shortcut icon\" href=\"favicon.png\" />\n    <link rel=\"apple-touch-icon\" href=\"apple-touch-icon.png\" />\n</head>\n\n<!--\n    \n    Body element is used by impress.js to set some useful class names, that will allow you to detect\n    the support and state of the presentation in CSS or other scripts.\n    \n    First very useful class name is `impress-not-supported`. This class means, that browser doesn't\n    support features required by impress.js, so you should apply some fallback styles in your CSS.\n    It's not necessary to add it manually on this element. If the script detects that browser is not\n    good enough it will add this class, but keeping it in HTML means that users without JavaScript\n    will also get fallback styles.\n    \n    When impress.js script detects that browser supports all required features, this class name will\n    be removed.\n    \n    The class name on body element also depends on currently active presentation step. More details about\n    it can be found later, when `hint` element is being described.\n    \n-->\n<body class=\"impress-not-supported\">\n\n<!--\n    For example this fallback message is only visible when there is `impress-not-supported` class on body.\n-->\n<div class=\"fallback-message\">\n    <p>Your browser <b>doesn't support the features required</b> by impress.js, so you are presented with a simplified version of this presentation.</p>\n    <p>For the best experience please use the latest <b>Chrome</b>, <b>Safari</b> or <b>Firefox</b> browser.</p>\n</div>\n\n<!--\n    \n    Now that's the core element used by impress.js.\n    \n    That's the wrapper for your presentation steps. In this element all the impress.js magic happens.\n    It doesn't have to be a `<div>`. Only `id` is important here as that's how the script find it.\n    \n    Often you don't need to use any attributes here, but for educational purposes I have used all of them below.\n\n    To change the duration of the transition between slides use `data-transition-duration=\"2000\"` giving it\n    a number of ms. It defaults to 1000 (1s).\n    \n    When authoring impress.js presentations, you should target some screen size, which you can define here.\n    The default is 1024 x 768. You should write your CSS as if this is the screen size used for the\n    presentation. When you present your presentation on a screen (or browser window) of different size,\n    impress.js will automatically scale the presentation to fit the screen. The minimum and maximum limits\n    to this scaling can also be defined here.\n    \n    NOTE: I intend to change the defaults to target HD screens in 2021. So you may want to make a habit\n    of explicitly defining these attributes for now, to avoid any disruption when the defaults change.\n    \n    You can also control the perspective with `data-perspective=\"500\"` giving it a number of pixels.\n    It defaults to 1000. You can set it to 0 if you don't want any 3D effects.\n    If you are willing to change this value make sure you understand how CSS perspective works:\n    https://developer.mozilla.org/en/CSS/perspective\n    \n    Plugins:\n    \n    We set the default time for autoplay plugin to 7 seconds. Autoplay will automatically advance\n    to next slide after a timeout expires.\n-->\n<div id=\"impress\"\n    data-transition-duration=\"1000\"\n    \n    data-width=\"1024\"\n    data-height=\"768\"\n    data-max-scale=\"3\"\n    data-min-scale=\"0\"\n    data-perspective=\"1000\"\n    \n    data-autoplay=\"7\">\n\n    <!--\n        \n        Here is where interesting thing start to happen.\n        \n        Each step of the presentation should be an element inside the `#impress` with a class name\n        of `step`. These step elements are positioned, rotated and scaled by impress.js, and\n        the 'camera' shows them on each step of the presentation.\n        \n        Positioning information is passed through data attributes.\n        \n        In the example below we only specify x and y position of the step element with `data-x=\"-1000\"`\n        and `data-y=\"-1500\"` attributes. This means that **the center** of the element (yes, the center)\n        will be positioned in point x = -1000px and y = -1500px of the presentation 'canvas'.\n        \n        It will not be rotated or scaled.\n        \n        --------\n        Plugins: For first slide, set the autoplay time to a custom 10 seconds.\n        \n    -->\n    <div id=\"bored\" class=\"step slide\" data-x=\"-1000\" data-y=\"-1500\" data-autoplay=\"10\">\n        <q>Aren’t you just <b>bored</b> with all those slides-based presentations?</q>\n    </div>\n\n    <!--\n        \n        The `id` attribute of the step element is used to identify it in the URL, but it's optional.\n        If it is not defined, it will get a default value of `step-N` where N is a number of slide.\n        \n        So in the example below it'll be `step-2`.\n        \n        The hash part of the url when this step is active will be `#/step-2`.\n        \n        You can also use `#step-2` in a link, to point directly to this particular step.\n        \n        Please note, that while `#/step-2` (with slash) would also work in a link it's not recommended.\n        Using classic `id`-based links like `#step-2` makes these links usable also in fallback mode.\n        \n    -->\n    <div class=\"step slide\" data-x=\"0\" data-y=\"-1500\">\n        <q>Don’t you think that presentations given <strong>in modern browsers</strong> shouldn’t <strong>copy the limits</strong> of ‘classic’ slide decks?</q>\n    </div>\n\n    <div class=\"step slide\" data-x=\"1000\" data-y=\"-1500\">\n        <q>Would you like to <strong>impress your audience</strong> with <strong>stunning visualization</strong> of your talk?</q>\n    </div>\n\n    <!--\n        \n        This is an example of step element being scaled.\n        \n        Again, we use a `data-` attribute, this time it's `data-scale=\"4\"`, so it means that this\n        element will be 4 times larger than the others.\n        From presentation and transitions point of view it means, that it will have to be scaled\n        down (4 times) to make it back to its correct size.\n        \n    -->\n    <div id=\"title\" class=\"step\" data-x=\"0\" data-y=\"0\" data-scale=\"4\">\n        <span class=\"try\">then you should try</span>\n        <h1>impress.js<sup>*</sup></h1>\n        <span class=\"footnote\"><sup>*</sup> no rhyme intended</span>\n    </div>\n\n    <!--\n        \n        This element introduces rotation.\n        \n        Notation shouldn't be a surprise. We use `data-rotate=\"90\"` attribute, meaning that this\n        element should be rotated by 90 degrees clockwise.\n        \n    -->\n    <div id=\"its\" class=\"step\" data-x=\"850\" data-y=\"3000\" data-rotate=\"90\" data-scale=\"5\">\n        <p>It’s a <strong>presentation tool</strong> <br/>\n        inspired by the idea behind <a href=\"http://prezi.com\">prezi.com</a> <br/>\n        and based on the <strong>power of CSS3 transforms and transitions</strong> in modern browsers.</p>\n    </div>\n\n    <div id=\"big\" class=\"step\" data-x=\"3500\" data-y=\"2100\" data-rotate=\"180\" data-scale=\"6\">\n        <p>visualize your <b>big</b> <span class=\"thoughts\">thoughts</span></p>\n    </div>\n\n    <!--\n        \n        And now it gets really exciting! We move into third dimension!\n        \n        Along with `data-x` and `data-y`, you can define the position on third (Z) axis, with\n        `data-z`. In the example below we use `data-z=\"-3000\"` meaning that element should be\n        positioned far away from us (by 3000px).\n        \n    -->\n    <div id=\"tiny\" class=\"step\" data-x=\"2825\" data-y=\"2325\" data-z=\"-3000\" data-rotate=\"300\" data-scale=\"1\">\n        <p>and <b>tiny</b> ideas</p>\n    </div>\n\n    <!--\n        \n        This step here doesn't introduce anything new when it comes to data attributes, but you\n        should notice in the demo that some words of this text are being animated.\n        It's a very basic CSS transition that is applied to the elements when this step element is\n        reached.\n        \n        At the very beginning of the presentation all step elements are given the class of `future`.\n        It means that they haven't been visited yet.\n        \n        When the presentation moves to given step `future` is changed to `present` class name.\n        That's how animation on this step works - text moves when the step has `present` class.\n        \n        Finally when the step is left the `present` class is removed from the element and `past`\n        class is added.\n        \n        So basically every step element has one of three classes: `future`, `present` and `past`.\n        Only one current step has the `present` class.\n        \n        Note: data-x/y/z attributes, if not defined, by default will inherit the value of the \n        previous step. So to get back to 0 on the z-axis, we must set it to 0.\n        See src/plugins/rel/README.md for more information.\n        \n    -->\n    <div id=\"ing\" class=\"step\" data-x=\"3500\" data-y=\"-850\" data-z=\"0\" data-rotate=\"270\" data-scale=\"6\">\n        <p>by <b class=\"positioning\">positioning</b>, <b class=\"rotating\">rotating</b> and <b class=\"scaling\">scaling</b> them on an infinite canvas</p>\n    </div>\n\n    <div id=\"imagination\" class=\"step\" data-x=\"6700\" data-y=\"-300\" data-scale=\"6\">\n        <p>the only <b>limit</b> is your <b class=\"imagination\">imagination</b></p>\n    </div>\n\n    <div id=\"source\" class=\"step\" data-x=\"6300\" data-y=\"2000\" data-rotate=\"20\" data-scale=\"4\">\n        <p>want to know more?</p>\n        <q><a href=\"http://github.com/impress/impress.js\">use the source</a>, Luke!</q>\n    </div>\n\n    <div id=\"one-more-thing\" class=\"step\" data-x=\"6000\" data-y=\"4000\" data-scale=\"2\">\n        <p>one more thing...</p>\n    </div>\n\n    <!--\n        \n        And the last one shows full power and flexibility of impress.js.\n        \n        You can not only position element in 3D, but also rotate it around any axis.\n        So this one here will get rotated by -40 degrees (40 degrees anticlockwise) around X axis and\n        10 degrees (clockwise) around Y axis.\n        \n        You can of course rotate it around Z axis with `data-rotate-z` - it has exactly the same effect\n        as `data-rotate` (these two are basically aliases).\n        \n    -->\n    <div id=\"its-in-3d\" class=\"step\" data-x=\"6200\" data-y=\"4300\" data-z=\"-100\" data-rotate-x=\"-40\" data-rotate-y=\"10\" data-scale=\"2\">\n        <p><span class=\"have\">have</span> <span class=\"you\">you</span> <span class=\"noticed\">noticed</span> <span class=\"its\">it’s</span> <span class=\"in\">in</span> <b>3D<sup>*</sup></b>?</p>\n        <span class=\"footnote\">* beat that, prezi ;)</span>\n    </div>\n\n    <!--\n        \n        So to summarize of all the possible attributes used to position presentation steps, we have:\n        \n        * `data-x`, `data-y`, `data-z` - they define the position of **the center** of step element on\n            the canvas in pixels; their default value is 0;\n        * `data-rotate-x`, `data-rotate-y`, 'data-rotate-z`, `data-rotate` - they define the rotation of\n            the element around given axis in degrees; their default value is 0; `data-rotate` and `data-rotate-z`\n            are exactly the same;\n        * `data-scale` - defines the scale of step element; default value is 1\n        \n        These values are used by impress.js in CSS transformation functions, so for more information consult\n        CSS transfrom docs: https://developer.mozilla.org/en/CSS/transform\n        \n    -->\n    <div id=\"overview\" class=\"step\" data-x=\"3000\" data-y=\"1500\" data-z=\"0\" data-scale=\"10\">\n    </div>\n\n</div>\n\n<!--\n    This is a UI plugin. You can read more about plugins in src/plugins/README.md.\n    For now, I'll just tell you that this adds some graphical controls to navigate the\n    presentation. In the CSS file you can style them as you want. We've put them bottom right.\n-->\n<div id=\"impress-toolbar\"></div>\n\n<!--\n    \n    Hint is not related to impress.js in any way.\n    \n    But it can show you how to use impress.js features in creative way.\n    \n    When the presentation step is shown (selected) its element gets the class of \"active\" and the body element\n    gets the class based on active step id `impress-on-ID` (where ID is the step's id)... It may not be\n    so clear because of all these \"ids\" in previous sentence, so for example when the first step (the one with\n    the id of `bored`) is active, body element gets a class of `impress-on-bored`.\n    \n    This class is used by this hint below. Check CSS file to see how it's shown with delayed CSS animation when\n    the first step of presentation is visible for a couple of seconds.\n    \n    ...\n    \n    And when it comes to this piece of JavaScript below ... kids, don't do this at home ;)\n    It's just a quick and dirty workaround to get different hint text for touch devices.\n    In a real world it should be at least placed in separate JS file ... and the touch content should be\n    probably just hidden somewhere in HTML - not hard-coded in the script.\n    \n    Just sayin' ;)\n    \n-->\n<div class=\"hint\">\n    <p>Use a spacebar or arrow keys to navigate. <br/>\n       Press 'P' to launch speaker console.</p>\n</div>\n<script>\nif (\"ontouchstart\" in document.documentElement) { \n    document.querySelector(\".hint\").innerHTML = \"<p>Swipe left or right to navigate</p>\";\n}\n</script>\n\n<!--\n    \n    Last, but not least.\n    \n    To make all described above really work, you need to include impress.js in the page.\n    I strongly encourage to minify it first.\n    \n    In here I just include full source of the script to make it more readable.\n    \n    You also need to call a `impress().init()` function to initialize impress.js presentation.\n    And you should do it in the end of your document. Not only because it's a good practice, but also\n    because it should be done when the whole document is ready.\n    Of course you can wrap it in any kind of \"DOM ready\" event, but I was too lazy to do so ;)\n    \n-->\n<script src=\"js/impress.js\"></script>\n<script>impress().init();</script>\n\n<!--\n    \n    The `impress()` function also gives you access to the API that controls the presentation.\n    \n    Just store the result of the call:\n    \n        var api = impress();\n    \n    and you will get three functions you can call:\n    \n        `api.init()` - initializes the presentation,\n        `api.next()` - moves to next step of the presentation,\n        `api.prev()` - moves to previous step of the presentation,\n        `api.goto( stepIndex | stepElementId | stepElement, [duration] )` - moves the presentation to the step given by its index number\n                id or the DOM element; second parameter can be used to define duration of the transition in ms,\n                but it's optional - if not provided default transition duration for the presentation will be used.\n    \n    You can also simply call `impress()` again to get the API, so `impress().next()` is also allowed.\n    Don't worry, it won't initialize the presentation again.\n    \n    For some example uses of this API check the last part of the source of impress.js where the API\n    is used in event handlers.\n    \n-->\n\n</body>\n</html>\n\n<!--\n    \n    Now you know more or less everything you need to build your first impress.js presentation, but before\n    you start...\n    \n    Oh, you've already cloned the code from GitHub?\n    \n    You have it open in text editor?\n    \n    Stop right there!\n    \n    That's not how you create awesome presentations. This is only a code. Implementation of the idea that\n    first needs to grow in your mind.\n    \n    So if you want to build great presentation take a pencil and piece of paper. And turn off the computer.\n    \n    Sketch, draw and write. Brainstorm your ideas on a paper. Try to build a mind-map of what you'd like\n    to present. It will get you closer and closer to the layout you'll build later with impress.js.\n    \n    Get back to the code only when you have your presentation ready on a paper. It doesn't make sense to do\n    it earlier, because you'll only waste your time fighting with positioning of useless points.\n    \n    If you think I'm crazy, please put your hands on a book called \"Presentation Zen\". It's all about \n    creating awesome and engaging presentations.\n    \n    Think about it. 'Cause impress.js may not help you, if you have nothing interesting to say.\n    \n-->\n\n<!--\n    \n    Are you still reading this?\n    \n    For real? I'm impressed! \n    \n    But now, take my advice and take some time off. Make yourself a cup of coffee, tea,\n    or anything you like to drink.\n    \n    Cheers!\n    \n-->\n\n"
  },
  {
    "path": "js/impress.js",
    "content": "// This file was automatically generated from files in src/ directory.\n\n/*! Licensed under MIT License - http://github.com/impress/impress.js */\n/**\n * impress.js\n *\n * impress.js is a presentation tool based on the power of CSS3 transforms and transitions\n * in modern browsers and inspired by the idea behind prezi.com.\n *\n *\n * Copyright 2011-2012 Bartek Szopka (@bartaz), 2016-2023 Henrik Ingo (@henrikingo)\n * and 70+ other contributors\n *\n * Released under the MIT License.\n *\n * ------------------------------------------------\n *  author:  Bartek Szopka, Henrik Ingo\n *  version: 2.0.0\n *  url:     http://impress.js.org\n *  source:  http://github.com/impress/impress.js/\n */\n\n// You are one of those who like to know how things work inside?\n// Let me show you the cogs that make impress.js run...\n( function( document, window ) {\n    \"use strict\";\n    var lib;\n\n    // HELPER FUNCTIONS\n\n    // `pfx` is a function that takes a standard CSS property name as a parameter\n    // and returns it's prefixed version valid for current browser it runs in.\n    // The code is heavily inspired by Modernizr http://www.modernizr.com/\n    var pfx = ( function() {\n\n        var style = document.createElement( \"dummy\" ).style,\n            prefixes = \"Webkit Moz O ms Khtml\".split( \" \" ),\n            memory = {};\n\n        return function( prop ) {\n            if ( typeof memory[ prop ] === \"undefined\" ) {\n\n                var ucProp  = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ),\n                    props   = ( prop + \" \" + prefixes.join( ucProp + \" \" ) + ucProp ).split( \" \" );\n\n                memory[ prop ] = null;\n                for ( var i in props ) {\n                    if ( style[ props[ i ] ] !== undefined ) {\n                        memory[ prop ] = props[ i ];\n                        break;\n                    }\n                }\n\n            }\n\n            return memory[ prop ];\n        };\n\n    } )();\n\n    var validateOrder = function( order, fallback ) {\n        var validChars = \"xyz\";\n        var returnStr = \"\";\n        if ( typeof order === \"string\" ) {\n            for ( var i in order.split( \"\" ) ) {\n                if ( validChars.indexOf( order[ i ] ) >= 0 ) {\n                    returnStr += order[ i ];\n\n                    // Each of x,y,z can be used only once.\n                    validChars = validChars.split( order[ i ] ).join( \"\" );\n                }\n            }\n        }\n        if ( returnStr ) {\n            return returnStr;\n        } else if ( fallback !== undefined ) {\n            return fallback;\n        } else {\n            return \"xyz\";\n        }\n    };\n\n    // `css` function applies the styles given in `props` object to the element\n    // given as `el`. It runs all property names through `pfx` function to make\n    // sure proper prefixed version of the property is used.\n    var css = function( el, props ) {\n        var key, pkey;\n        for ( key in props ) {\n            if ( props.hasOwnProperty( key ) ) {\n                pkey = pfx( key );\n                if ( pkey !== null ) {\n                    el.style[ pkey ] = props[ key ];\n                }\n            }\n        }\n        return el;\n    };\n\n    // `translate` builds a translate transform string for given data.\n    var translate = function( t ) {\n        return \" translate3d(\" + t.x + \"px,\" + t.y + \"px,\" + t.z + \"px) \";\n    };\n\n    // `rotate` builds a rotate transform string for given data.\n    // By default the rotations are in X Y Z order that can be reverted by passing `true`\n    // as second parameter.\n    var rotate = function( r, revert ) {\n        var order = r.order ? r.order : \"xyz\";\n        var css = \"\";\n        var axes = order.split( \"\" );\n        if ( revert ) {\n            axes = axes.reverse();\n        }\n\n        for ( var i = 0; i < axes.length; i++ ) {\n            css += \" rotate\" + axes[ i ].toUpperCase() + \"(\" + r[ axes[ i ] ] + \"deg)\";\n        }\n        return css;\n    };\n\n    // `scale` builds a scale transform string for given data.\n    var scale = function( s ) {\n        return \" scale(\" + s + \") \";\n    };\n\n    // `computeWindowScale` counts the scale factor between window size and size\n    // defined for the presentation in the config.\n    var computeWindowScale = function( config ) {\n        var hScale = window.innerHeight / config.height,\n            wScale = window.innerWidth / config.width,\n            scale = hScale > wScale ? wScale : hScale;\n\n        if ( config.maxScale && scale > config.maxScale ) {\n            scale = config.maxScale;\n        }\n\n        if ( config.minScale && scale < config.minScale ) {\n            scale = config.minScale;\n        }\n\n        return scale;\n    };\n\n    // CHECK SUPPORT\n    var body = document.body;\n    var impressSupported =\n\n                          // Browser should support CSS 3D transtorms\n                           ( pfx( \"perspective\" ) !== null ) &&\n\n                          // And `classList` and `dataset` APIs\n                           ( body.classList ) &&\n                           ( body.dataset );\n\n    if ( !impressSupported ) {\n\n        // We can't be sure that `classList` is supported\n        body.className += \" impress-not-supported \";\n    }\n\n    // GLOBALS AND DEFAULTS\n\n    // This is where the root elements of all impress.js instances will be kept.\n    // Yes, this means you can have more than one instance on a page, but I'm not\n    // sure if it makes any sense in practice ;)\n    var roots = {};\n\n    var preInitPlugins = [];\n    var preStepLeavePlugins = [];\n\n    // Some default config values.\n    var defaults = {\n        width: 1920,\n        height: 1080,\n        maxScale: 3,\n        minScale: 0,\n\n        perspective: 1000,\n\n        transitionDuration: 1000\n    };\n\n    // Configuration options\n    var config = null;\n\n    // It's just an empty function ... and a useless comment.\n    var empty = function() { return false; };\n\n    // IMPRESS.JS API\n\n    // And that's where interesting things will start to happen.\n    // It's the core `impress` function that returns the impress.js API\n    // for a presentation based on the element with given id (\"impress\"\n    // by default).\n    var impress = window.impress = function( rootId ) {\n\n        // If impress.js is not supported by the browser return a dummy API\n        // it may not be a perfect solution but we return early and avoid\n        // running code that may use features not implemented in the browser.\n        if ( !impressSupported ) {\n            return {\n                init: empty,\n                goto: empty,\n                prev: empty,\n                next: empty,\n                swipe: empty,\n                tear: empty,\n                lib: {}\n            };\n        }\n\n        rootId = rootId || \"impress\";\n\n        // If given root is already initialized just return the API\n        if ( roots[ \"impress-root-\" + rootId ] ) {\n            return roots[ \"impress-root-\" + rootId ];\n        }\n\n        // The gc library depends on being initialized before we do any changes to DOM.\n        lib = initLibraries( rootId );\n\n        body.classList.remove( \"impress-not-supported\" );\n        body.classList.add( \"impress-supported\" );\n\n        // Data of all presentation steps\n        var stepsData = {};\n\n        // Element of currently active step\n        var activeStep = null;\n\n        // Current state (position, rotation and scale) of the presentation\n        var currentState = null;\n\n        // Array of step elements\n        var steps = null;\n\n        // Scale factor of the browser window\n        var windowScale = null;\n\n        // Root presentation elements\n        var root = lib.util.byId( rootId );\n        var canvas = document.createElement( \"div\" );\n\n        var initialized = false;\n\n        // STEP EVENTS\n        //\n        // There are currently two step events triggered by impress.js\n        // `impress:stepenter` is triggered when the step is shown on the\n        // screen (the transition from the previous one is finished) and\n        // `impress:stepleave` is triggered when the step is left (the\n        // transition to next step just starts).\n\n        // Reference to last entered step\n        var lastEntered = null;\n\n        // `onStepEnter` is called whenever the step element is entered\n        // but the event is triggered only if the step is different than\n        // last entered step.\n        // We sometimes call `goto`, and therefore `onStepEnter`, just to redraw a step, such as\n        // after screen resize. In this case - more precisely, in any case - we trigger a\n        // `impress:steprefresh` event.\n        var onStepEnter = function( step ) {\n            if ( lastEntered !== step ) {\n                lib.util.triggerEvent( step, \"impress:stepenter\" );\n                lastEntered = step;\n            }\n            lib.util.triggerEvent( step, \"impress:steprefresh\" );\n        };\n\n        // `onStepLeave` is called whenever the currentStep element is left\n        // but the event is triggered only if the currentStep is the same as\n        // lastEntered step.\n        var onStepLeave = function( currentStep, nextStep ) {\n            if ( lastEntered === currentStep ) {\n                lib.util.triggerEvent( currentStep, \"impress:stepleave\", { next: nextStep } );\n                lastEntered = null;\n            }\n        };\n\n        // `initStep` initializes given step element by reading data from its\n        // data attributes and setting correct styles.\n        var initStep = function( el, idx ) {\n            var data = el.dataset,\n                step = {\n                    translate: {\n                        x: lib.util.toNumberAdvanced( data.x ),\n                        y: lib.util.toNumberAdvanced( data.y ),\n                        z: lib.util.toNumberAdvanced( data.z )\n                    },\n                    rotate: {\n                        x: lib.util.toNumber( data.rotateX ),\n                        y: lib.util.toNumber( data.rotateY ),\n                        z: lib.util.toNumber( data.rotateZ || data.rotate ),\n                        order: validateOrder( data.rotateOrder )\n                    },\n                    scale: lib.util.toNumber( data.scale, 1 ),\n                    transitionDuration: lib.util.toNumber(\n                        data.transitionDuration, config.transitionDuration\n                    ),\n                    el: el\n                };\n\n            if ( !el.id ) {\n                el.id = \"step-\" + ( idx + 1 );\n            }\n\n            stepsData[ \"impress-\" + el.id ] = step;\n\n            css( el, {\n                position: \"absolute\",\n                transform: \"translate(-50%,-50%)\" +\n                           translate( step.translate ) +\n                           rotate( step.rotate ) +\n                           scale( step.scale ),\n                transformStyle: \"preserve-3d\"\n            } );\n        };\n\n        // Initialize all steps.\n        // Read the data-* attributes, store in internal stepsData, and render with CSS.\n        var initAllSteps = function() {\n            steps = lib.util.$$( \".step\", root );\n            steps.forEach( initStep );\n        };\n\n        // Build configuration from root and defaults\n        var buildConfig = function() {\n            var rootData = root.dataset;\n            return {\n                width: lib.util.toNumber( rootData.width, defaults.width ),\n                height: lib.util.toNumber( rootData.height, defaults.height ),\n                maxScale: lib.util.toNumber( rootData.maxScale, defaults.maxScale ),\n                minScale: lib.util.toNumber( rootData.minScale, defaults.minScale ),\n                perspective: lib.util.toNumber( rootData.perspective, defaults.perspective ),\n                transitionDuration: lib.util.toNumber(\n                    rootData.transitionDuration, defaults.transitionDuration\n                )\n            };\n        };\n\n        // `init` API function that initializes (and runs) the presentation.\n        var init = function() {\n            if ( initialized ) { return; }\n\n            // Initialize the configuration object, so it can be used by pre-init plugins.\n            config = buildConfig();\n            execPreInitPlugins( root );\n\n            // First we set up the viewport for mobile devices.\n            // For some reason iPad goes nuts when it is not done properly.\n            var meta = lib.util.$( \"meta[name='viewport']\" ) || document.createElement( \"meta\" );\n            meta.content = \"width=device-width, minimum-scale=1, maximum-scale=1, user-scalable=no\";\n            if ( meta.parentNode !== document.head ) {\n                meta.name = \"viewport\";\n                document.head.appendChild( meta );\n            }\n\n            windowScale = computeWindowScale( config );\n\n            // Wrap steps with \"canvas\" element\n            lib.util.arrayify( root.childNodes ).forEach( function( el ) {\n                canvas.appendChild( el );\n            } );\n            root.appendChild( canvas );\n\n            // Set initial styles\n            document.documentElement.style.height = \"100%\";\n\n            css( body, {\n                height: \"100%\",\n                overflow: \"hidden\"\n            } );\n\n            var rootStyles = {\n                position: \"absolute\",\n                transformOrigin: \"top left\",\n                transition: \"all 0s ease-in-out\",\n                transformStyle: \"preserve-3d\"\n            };\n\n            css( root, rootStyles );\n            css( root, {\n                top: \"50%\",\n                left: \"50%\",\n                perspective: ( config.perspective / windowScale ) + \"px\",\n                transform: scale( windowScale )\n            } );\n            css( canvas, rootStyles );\n\n            body.classList.remove( \"impress-disabled\" );\n            body.classList.add( \"impress-enabled\" );\n\n            // Get and init steps\n            initAllSteps();\n\n            // Set a default initial state of the canvas\n            currentState = {\n                translate: { x: 0, y: 0, z: 0 },\n                rotate:    { x: 0, y: 0, z: 0, order: \"xyz\" },\n                scale:     1\n            };\n\n            initialized = true;\n\n            lib.util.triggerEvent( root, \"impress:init\",\n                                   { api: roots[ \"impress-root-\" + rootId ] } );\n        };\n\n        // `getStep` is a helper function that returns a step element defined by parameter.\n        // If a number is given, step with index given by the number is returned, if a string\n        // is given step element with such id is returned, if DOM element is given it is returned\n        // if it is a correct step element.\n        var getStep = function( step ) {\n            if ( typeof step === \"number\" ) {\n                step = step < 0 ? steps[ steps.length + step ] : steps[ step ];\n            } else if ( typeof step === \"string\" ) {\n                step = lib.util.byId( step );\n            }\n            return ( step && step.id && stepsData[ \"impress-\" + step.id ] ) ? step : null;\n        };\n\n        // Used to reset timeout for `impress:stepenter` event\n        var stepEnterTimeout = null;\n\n        // `goto` API function that moves to step given as `el` parameter (by index, id or element).\n        // `duration` optionally given as second parameter, is the transition duration in css.\n        // `reason` is the string \"next\", \"prev\" or \"goto\" (default) and will be made available to\n        // preStepLeave plugins.\n        // `origEvent` may contain event that caused the call to goto, such as a key press event\n        var goto = function( el, duration, reason, origEvent ) {\n            reason = reason || \"goto\";\n            origEvent = origEvent || null;\n\n            if ( !initialized ) {\n                return false;\n            }\n\n            // Re-execute initAllSteps for each transition. This allows to edit step attributes\n            // dynamically, such as change their coordinates, or even remove or add steps, and have\n            // that change apply when goto() is called.\n            initAllSteps();\n\n            if ( !( el = getStep( el ) ) ) {\n                return false;\n            }\n\n            // Sometimes it's possible to trigger focus on first link with some keyboard action.\n            // Browser in such a case tries to scroll the page to make this element visible\n            // (even that body overflow is set to hidden) and it breaks our careful positioning.\n            //\n            // So, as a lousy (and lazy) workaround we will make the page scroll back to the top\n            // whenever slide is selected\n            //\n            // If you are reading this and know any better way to handle it, I'll be glad to hear\n            // about it!\n            window.scrollTo( 0, 0 );\n\n            var step = stepsData[ \"impress-\" + el.id ];\n            duration = ( duration !== undefined ? duration : step.transitionDuration );\n\n            // If we are in fact moving to another step, start with executing the registered\n            // preStepLeave plugins.\n            if ( activeStep && activeStep !== el ) {\n                var event = { target: activeStep, detail: {} };\n                event.detail.next = el;\n                event.detail.transitionDuration = duration;\n                event.detail.reason = reason;\n                if ( origEvent ) {\n                    event.origEvent = origEvent;\n                }\n\n                if ( execPreStepLeavePlugins( event ) === false ) {\n\n                    // PreStepLeave plugins are allowed to abort the transition altogether, by\n                    // returning false.\n                    // see stop and substep plugins for an example of doing just that\n                    return false;\n                }\n\n                // Plugins are allowed to change the detail values\n                el = event.detail.next;\n                step = stepsData[ \"impress-\" + el.id ];\n                duration = event.detail.transitionDuration;\n            }\n\n            if ( activeStep ) {\n                activeStep.classList.remove( \"active\" );\n                body.classList.remove( \"impress-on-\" + activeStep.id );\n            }\n            el.classList.add( \"active\" );\n\n            body.classList.add( \"impress-on-\" + el.id );\n\n            // Compute target state of the canvas based on given step\n            var target = {\n                rotate: {\n                    x: -step.rotate.x,\n                    y: -step.rotate.y,\n                    z: -step.rotate.z,\n                    order: step.rotate.order\n                },\n                translate: {\n                    x: -step.translate.x,\n                    y: -step.translate.y,\n                    z: -step.translate.z\n                },\n                scale: 1 / step.scale\n            };\n\n            // Check if the transition is zooming in or not.\n            //\n            // This information is used to alter the transition style:\n            // when we are zooming in - we start with move and rotate transition\n            // and the scaling is delayed, but when we are zooming out we start\n            // with scaling down and move and rotation are delayed.\n            var zoomin = target.scale >= currentState.scale;\n\n            duration = lib.util.toNumber( duration, config.transitionDuration );\n            var delay = ( duration / 2 );\n\n            // If the same step is re-selected, force computing window scaling,\n            // because it is likely to be caused by window resize\n            if ( el === activeStep ) {\n                windowScale = computeWindowScale( config );\n            }\n\n            var targetScale = target.scale * windowScale;\n\n            // Trigger leave of currently active element (if it's not the same step again)\n            if ( activeStep && activeStep !== el ) {\n                onStepLeave( activeStep, el );\n            }\n\n            // Now we alter transforms of `root` and `canvas` to trigger transitions.\n            //\n            // And here is why there are two elements: `root` and `canvas` - they are\n            // being animated separately:\n            // `root` is used for scaling and `canvas` for translate and rotations.\n            // Transitions on them are triggered with different delays (to make\n            // visually nice and \"natural\" looking transitions), so we need to know\n            // that both of them are finished.\n            css( root, {\n\n                // To keep the perspective look similar for different scales\n                // we need to \"scale\" the perspective, too\n                // For IE 11 support we must specify perspective independent\n                // of transform.\n                perspective: ( config.perspective / targetScale ) + \"px\",\n                transform: scale( targetScale ),\n                transitionDuration: duration + \"ms\",\n                transitionDelay: ( zoomin ? delay : 0 ) + \"ms\"\n            } );\n\n            css( canvas, {\n                transform: rotate( target.rotate, true ) + translate( target.translate ),\n                transitionDuration: duration + \"ms\",\n                transitionDelay: ( zoomin ? 0 : delay ) + \"ms\"\n            } );\n\n            // Here is a tricky part...\n            //\n            // If there is no change in scale or no change in rotation and translation, it means\n            // there was actually no delay - because there was no transition on `root` or `canvas`\n            // elements. We want to trigger `impress:stepenter` event in the correct moment, so\n            // here we compare the current and target values to check if delay should be taken into\n            // account.\n            //\n            // I know that this `if` statement looks scary, but it's pretty simple when you know\n            // what is going on - it's simply comparing all the values.\n            if ( currentState.scale === target.scale ||\n                ( currentState.rotate.x === target.rotate.x &&\n                  currentState.rotate.y === target.rotate.y &&\n                  currentState.rotate.z === target.rotate.z &&\n                  currentState.translate.x === target.translate.x &&\n                  currentState.translate.y === target.translate.y &&\n                  currentState.translate.z === target.translate.z ) ) {\n                delay = 0;\n            }\n\n            // Store current state\n            currentState = target;\n            activeStep = el;\n\n            // And here is where we trigger `impress:stepenter` event.\n            // We simply set up a timeout to fire it taking transition duration (and possible delay)\n            // into account.\n            //\n            // I really wanted to make it in more elegant way. The `transitionend` event seemed to\n            // be the best way to do it, but the fact that I'm using transitions on two separate\n            // elements and that the `transitionend` event is only triggered when there was a\n            // transition (change in the values) caused some bugs and made the code really\n            // complicated, cause I had to handle all the conditions separately. And it still\n            // needed a `setTimeout` fallback for the situations when there is no transition at all.\n            // So I decided that I'd rather make the code simpler than use shiny new\n            // `transitionend`.\n            //\n            // If you want learn something interesting and see how it was done with `transitionend`\n            // go back to version 0.5.2 of impress.js:\n            // http://github.com/bartaz/impress.js/blob/0.5.2/js/impress.js\n            window.clearTimeout( stepEnterTimeout );\n            stepEnterTimeout = window.setTimeout( function() {\n                onStepEnter( activeStep );\n            }, duration + delay );\n\n            return el;\n        };\n\n        // `prev` API function goes to previous step (in document order)\n        // `event` is optional, may contain the event that caused the need to call prev()\n        var prev = function( origEvent ) {\n            var prev = steps.indexOf( activeStep ) - 1;\n            prev = prev >= 0 ? steps[ prev ] : steps[ steps.length - 1 ];\n\n            return goto( prev, undefined, \"prev\", origEvent );\n        };\n\n        // `next` API function goes to next step (in document order)\n        // `event` is optional, may contain the event that caused the need to call next()\n        var next = function( origEvent ) {\n            var next = steps.indexOf( activeStep ) + 1;\n            next = next < steps.length ? steps[ next ] : steps[ 0 ];\n\n            return goto( next, undefined, \"next\", origEvent );\n        };\n\n        // Swipe for touch devices by @and3rson.\n        // Below we extend the api to control the animation between the currently\n        // active step and a presumed next/prev step. See touch plugin for\n        // an example of using this api.\n\n        // Helper function\n        var interpolate = function( a, b, k ) {\n            return a + ( b - a ) * k;\n        };\n\n        // Animate a swipe.\n        //\n        // Pct is a value between -1.0 and +1.0, designating the current length\n        // of the swipe.\n        //\n        // If pct is negative, swipe towards the next() step, if positive,\n        // towards the prev() step.\n        //\n        // Note that pre-stepleave plugins such as goto can mess with what is a\n        // next() and prev() step, so we need to trigger the pre-stepleave event\n        // here, even if a swipe doesn't guarantee that the transition will\n        // actually happen.\n        //\n        // Calling swipe(), with any value of pct, won't in itself cause a\n        // transition to happen, this is just to animate the swipe. Once the\n        // transition is committed - such as at a touchend event - caller is\n        // responsible for also calling prev()/next() as appropriate.\n        //\n        // Note: For now, this function is made available to be used by the swipe plugin (which\n        // is the UI counterpart to this). It is a semi-internal API and intentionally not\n        // documented in DOCUMENTATION.md.\n        var swipe = function( pct ) {\n            if ( Math.abs( pct ) > 1 ) {\n                return;\n            }\n\n            // Prepare & execute the preStepLeave event\n            var event = { target: activeStep, detail: {} };\n            event.detail.swipe = pct;\n\n            // Will be ignored within swipe animation, but just in case a plugin wants to read this,\n            // humor them\n            event.detail.transitionDuration = config.transitionDuration;\n            var idx; // Needed by jshint\n            if ( pct < 0 ) {\n                idx = steps.indexOf( activeStep ) + 1;\n                event.detail.next = idx < steps.length ? steps[ idx ] : steps[ 0 ];\n                event.detail.reason = \"next\";\n            } else if ( pct > 0 ) {\n                idx = steps.indexOf( activeStep ) - 1;\n                event.detail.next = idx >= 0 ? steps[ idx ] : steps[ steps.length - 1 ];\n                event.detail.reason = \"prev\";\n            } else {\n\n                // No move\n                return;\n            }\n            if ( execPreStepLeavePlugins( event ) === false ) {\n\n                // If a preStepLeave plugin wants to abort the transition, don't animate a swipe\n                // For stop, this is probably ok. For substep, the plugin it self might want to do\n                // some animation, but that's not the current implementation.\n                return false;\n            }\n            var nextElement = event.detail.next;\n\n            var nextStep = stepsData[ \"impress-\" + nextElement.id ];\n\n            // If the same step is re-selected, force computing window scaling,\n            var nextScale = nextStep.scale * windowScale;\n            var k = Math.abs( pct );\n\n            var interpolatedStep = {\n                translate: {\n                    x: interpolate( currentState.translate.x, -nextStep.translate.x, k ),\n                    y: interpolate( currentState.translate.y, -nextStep.translate.y, k ),\n                    z: interpolate( currentState.translate.z, -nextStep.translate.z, k )\n                },\n                rotate: {\n                    x: interpolate( currentState.rotate.x, -nextStep.rotate.x, k ),\n                    y: interpolate( currentState.rotate.y, -nextStep.rotate.y, k ),\n                    z: interpolate( currentState.rotate.z, -nextStep.rotate.z, k ),\n\n                    // Unfortunately there's a discontinuity if rotation order changes. Nothing I\n                    // can do about it?\n                    order: k < 0.7 ? currentState.rotate.order : nextStep.rotate.order\n                },\n                scale: interpolate( currentState.scale * windowScale, nextScale, k )\n            };\n\n            css( root, {\n\n                // To keep the perspective look similar for different scales\n                // we need to 'scale' the perspective, too\n                perspective: config.perspective / interpolatedStep.scale + \"px\",\n                transform: scale( interpolatedStep.scale ),\n                transitionDuration: \"0ms\",\n                transitionDelay: \"0ms\"\n            } );\n\n            css( canvas, {\n                transform: rotate( interpolatedStep.rotate, true ) +\n                           translate( interpolatedStep.translate ),\n                transitionDuration: \"0ms\",\n                transitionDelay: \"0ms\"\n            } );\n        };\n\n        // Teardown impress\n        // Resets the DOM to the state it was before impress().init() was called.\n        // (If you called impress(rootId).init() for multiple different rootId's, then you must\n        // also call tear() once for each of them.)\n        var tear = function() {\n            lib.gc.teardown();\n            delete roots[ \"impress-root-\" + rootId ];\n        };\n\n        // Adding some useful classes to step elements.\n        //\n        // All the steps that have not been shown yet are given `future` class.\n        // When the step is entered the `future` class is removed and the `present`\n        // class is given. When the step is left `present` class is replaced with\n        // `past` class.\n        //\n        // So every step element is always in one of three possible states:\n        // `future`, `present` and `past`.\n        //\n        // There classes can be used in CSS to style different types of steps.\n        // For example the `present` class can be used to trigger some custom\n        // animations when step is shown.\n        lib.gc.addEventListener( root, \"impress:init\", function() {\n\n            // STEP CLASSES\n            steps.forEach( function( step ) {\n                step.classList.add( \"future\" );\n            } );\n\n            lib.gc.addEventListener( root, \"impress:stepenter\", function( event ) {\n                event.target.classList.remove( \"past\" );\n                event.target.classList.remove( \"future\" );\n                event.target.classList.add( \"present\" );\n            }, false );\n\n            lib.gc.addEventListener( root, \"impress:stepleave\", function( event ) {\n                event.target.classList.remove( \"present\" );\n                event.target.classList.add( \"past\" );\n            }, false );\n\n        }, false );\n\n        // Adding hash change support.\n        lib.gc.addEventListener( root, \"impress:init\", function() {\n\n            // Last hash detected\n            var lastHash = \"\";\n\n            // `#/step-id` is used instead of `#step-id` to prevent default browser\n            // scrolling to element in hash.\n            //\n            // And it has to be set after animation finishes, because in Chrome it\n            // makes transition laggy.\n            // BUG: http://code.google.com/p/chromium/issues/detail?id=62820\n            lib.gc.addEventListener( root, \"impress:stepenter\", function( event ) {\n                window.location.hash = lastHash = \"#/\" + event.target.id;\n            }, false );\n\n            lib.gc.addEventListener( window, \"hashchange\", function() {\n\n                // When the step is entered hash in the location is updated\n                // (just few lines above from here), so the hash change is\n                // triggered and we would call `goto` again on the same element.\n                //\n                // To avoid this we store last entered hash and compare.\n                if ( window.location.hash !== lastHash ) {\n                    goto( lib.util.getElementFromHash() );\n                }\n            }, false );\n\n            // START\n            // by selecting step defined in url or first step of the presentation\n            goto( lib.util.getElementFromHash() || steps[ 0 ], 0 );\n        }, false );\n\n        body.classList.add( \"impress-disabled\" );\n\n        // Store and return API for given impress.js root element\n        return ( roots[ \"impress-root-\" + rootId ] = {\n            init: init,\n            goto: goto,\n            next: next,\n            prev: prev,\n            swipe: swipe,\n            tear: tear,\n            lib: lib\n        } );\n\n    };\n\n    // Flag that can be used in JS to check if browser have passed the support test\n    impress.supported = impressSupported;\n\n    // ADD and INIT LIBRARIES\n    // Library factories are defined in src/lib/*.js, and register themselves by calling\n    // impress.addLibraryFactory(libraryFactoryObject). They're stored here, and used to augment\n    // the API with library functions when client calls impress(rootId).\n    // See src/lib/README.md for clearer example.\n    // (Advanced usage: For different values of rootId, a different instance of the libaries are\n    // generated, in case they need to hold different state for different root elements.)\n    var libraryFactories = {};\n    impress.addLibraryFactory = function( obj ) {\n        for ( var libname in obj ) {\n            if ( obj.hasOwnProperty( libname ) ) {\n                libraryFactories[ libname ] = obj[ libname ];\n            }\n        }\n    };\n\n    // Call each library factory, and return the lib object that is added to the api.\n    var initLibraries = function( rootId ) { //jshint ignore:line\n        var lib = {};\n        for ( var libname in libraryFactories ) {\n            if ( libraryFactories.hasOwnProperty( libname ) ) {\n                if ( lib[ libname ] !== undefined ) {\n                    throw \"impress.js ERROR: Two libraries both tried to use libname: \" +  libname;\n                }\n                lib[ libname ] = libraryFactories[ libname ]( rootId );\n            }\n        }\n        return lib;\n    };\n\n    // `addPreInitPlugin` allows plugins to register a function that should\n    // be run (synchronously) at the beginning of init, before\n    // impress().init() itself executes.\n    impress.addPreInitPlugin = function( plugin, weight ) {\n        weight = parseInt( weight ) || 10;\n        if ( weight <= 0 ) {\n            throw \"addPreInitPlugin: weight must be a positive integer\";\n        }\n\n        if ( preInitPlugins[ weight ] === undefined ) {\n            preInitPlugins[ weight ] = [];\n        }\n        preInitPlugins[ weight ].push( plugin );\n    };\n\n    // Called at beginning of init, to execute all pre-init plugins.\n    var execPreInitPlugins = function( root ) { //jshint ignore:line\n        for ( var i = 0; i < preInitPlugins.length; i++ ) {\n            var thisLevel = preInitPlugins[ i ];\n            if ( thisLevel !== undefined ) {\n                for ( var j = 0; j < thisLevel.length; j++ ) {\n                    thisLevel[ j ]( root, roots[ \"impress-root-\" + root.id ] );\n                }\n            }\n        }\n    };\n\n    // `addPreStepLeavePlugin` allows plugins to register a function that should\n    // be run (synchronously) at the beginning of goto()\n    impress.addPreStepLeavePlugin = function( plugin, weight ) { //jshint ignore:line\n        weight = parseInt( weight ) || 10;\n        if ( weight <= 0 ) {\n            throw \"addPreStepLeavePlugin: weight must be a positive integer\";\n        }\n\n        if ( preStepLeavePlugins[ weight ] === undefined ) {\n            preStepLeavePlugins[ weight ] = [];\n        }\n        preStepLeavePlugins[ weight ].push( plugin );\n    };\n\n    impress.getConfig = function() {\n        return config;\n    };\n\n    // Called at beginning of goto(), to execute all preStepLeave plugins.\n    var execPreStepLeavePlugins = function( event ) { //jshint ignore:line\n        for ( var i = 0; i < preStepLeavePlugins.length; i++ ) {\n            var thisLevel = preStepLeavePlugins[ i ];\n            if ( thisLevel !== undefined ) {\n                for ( var j = 0; j < thisLevel.length; j++ ) {\n                    if ( thisLevel[ j ]( event ) === false ) {\n\n                        // If a plugin returns false, the stepleave event (and related transition)\n                        // is aborted\n                        return false;\n                    }\n                }\n            }\n        }\n    };\n\n} )( document, window );\n\n// THAT'S ALL FOLKS!\n//\n// Thanks for reading it all.\n// Or thanks for scrolling down and reading the last part.\n//\n// I've learnt a lot when building impress.js and I hope this code and comments\n// will help somebody learn at least some part of it.\n\n/**\n * Garbage collection utility\n *\n * This library allows plugins to add elements and event listeners they add to the DOM. The user\n * can call `impress().lib.gc.teardown()` to cause all of them to be removed from DOM, so that\n * the document is in the state it was before calling `impress().init()`.\n *\n * In addition to just adding elements and event listeners to the garbage collector, plugins\n * can also register callback functions to do arbitrary cleanup upon teardown.\n *\n * Henrik Ingo (c) 2016\n * MIT License\n */\n\n( function( document, window ) {\n    \"use strict\";\n    var roots = [];\n    var rootsCount = 0;\n    var startingState = { roots: [] };\n\n    var libraryFactory = function( rootId ) {\n        if ( roots[ rootId ] ) {\n            return roots[ rootId ];\n        }\n\n        // Per root global variables (instance variables?)\n        var elementList = [];\n        var eventListenerList = [];\n        var callbackList = [];\n\n        recordStartingState( rootId );\n\n        // LIBRARY FUNCTIONS\n        // Definitions of the library functions we return as an object at the end\n\n        // `pushElement` adds a DOM element to the gc stack\n        var pushElement = function( element ) {\n            elementList.push( element );\n        };\n\n        // `appendChild` is a convenience wrapper that combines DOM appendChild with gc.pushElement\n        var appendChild = function( parent, element ) {\n            parent.appendChild( element );\n            pushElement( element );\n        };\n\n        // `pushEventListener` adds an event listener to the gc stack\n        var pushEventListener = function( target, type, listenerFunction ) {\n            eventListenerList.push( { target:target, type:type, listener:listenerFunction } );\n        };\n\n        // `addEventListener` combines DOM addEventListener with gc.pushEventListener\n        var addEventListener = function( target, type, listenerFunction ) {\n            target.addEventListener( type, listenerFunction );\n            pushEventListener( target, type, listenerFunction );\n        };\n\n        // `pushCallback` If the above utilities are not enough, plugins can add their own callback\n        // function to do arbitrary things.\n        var pushCallback = function( callback ) {\n            callbackList.push( callback );\n        };\n        pushCallback( function( rootId ) { resetStartingState( rootId ); } );\n\n        // `teardown` will\n        // - execute all callbacks in LIFO order\n        // - call `removeChild` on all DOM elements in LIFO order\n        // - call `removeEventListener` on all event listeners in LIFO order\n        // The goal of a teardown is to return to the same state that the DOM was before\n        // `impress().init()` was called.\n        var teardown = function() {\n\n            // Execute the callbacks in LIFO order\n            var i; // Needed by jshint\n            for ( i = callbackList.length - 1; i >= 0; i-- ) {\n                callbackList[ i ]( rootId );\n            }\n            callbackList = [];\n            for ( i = 0; i < elementList.length; i++ ) {\n                elementList[ i ].parentElement.removeChild( elementList[ i ] );\n            }\n            elementList = [];\n            for ( i = 0; i < eventListenerList.length; i++ ) {\n                var target   = eventListenerList[ i ].target;\n                var type     = eventListenerList[ i ].type;\n                var listener = eventListenerList[ i ].listener;\n                target.removeEventListener( type, listener );\n            }\n        };\n\n        var lib = {\n            pushElement: pushElement,\n            appendChild: appendChild,\n            pushEventListener: pushEventListener,\n            addEventListener: addEventListener,\n            pushCallback: pushCallback,\n            teardown: teardown\n        };\n        roots[ rootId ] = lib;\n        rootsCount++;\n        return lib;\n    };\n\n    // Let impress core know about the existence of this library\n    window.impress.addLibraryFactory( { gc: libraryFactory } );\n\n    // CORE INIT\n    // The library factory (gc(rootId)) is called at the beginning of impress(rootId).init()\n    // For the purposes of teardown(), we can use this as an opportunity to save the state\n    // of a few things in the DOM in their virgin state, before impress().init() did anything.\n    // Note: These could also be recorded by the code in impress.js core as these values\n    // are changed, but in an effort to not deviate too much from upstream, I'm adding\n    // them here rather than the core itself.\n    var recordStartingState = function( rootId ) {\n        startingState.roots[ rootId ] = {};\n        startingState.roots[ rootId ].steps = [];\n\n        // Record whether the steps have an id or not\n        var steps = document.getElementById( rootId ).querySelectorAll( \".step\" );\n        for ( var i = 0; i < steps.length; i++ ) {\n            var el = steps[ i ];\n            startingState.roots[ rootId ].steps.push( {\n                el: el,\n                id: el.getAttribute( \"id\" )\n            } );\n        }\n\n        // In the rare case of multiple roots, the following is changed on first init() and\n        // reset at last tear().\n        if ( rootsCount === 0 ) {\n            startingState.body = {};\n\n            // It is customary for authors to set body.class=\"impress-not-supported\" as a starting\n            // value, which can then be removed by impress().init(). But it is not required.\n            // Remember whether it was there or not.\n            if ( document.body.classList.contains( \"impress-not-supported\" ) ) {\n                startingState.body.impressNotSupported = true;\n            } else {\n                startingState.body.impressNotSupported = false;\n            }\n\n            // If there's a <meta name=\"viewport\"> element, its contents will be overwritten by init\n            var metas = document.head.querySelectorAll( \"meta\" );\n            for ( i = 0; i < metas.length; i++ ) {\n                var m = metas[ i ];\n                if ( m.name === \"viewport\" ) {\n                    startingState.meta = m.content;\n                }\n            }\n        }\n    };\n\n    // CORE TEARDOWN\n    var resetStartingState = function( rootId ) {\n\n        // Reset body element\n        document.body.classList.remove( \"impress-enabled\" );\n        document.body.classList.remove( \"impress-disabled\" );\n\n        var root = document.getElementById( rootId );\n        var activeId = root.querySelector( \".active\" ).id;\n        document.body.classList.remove( \"impress-on-\" + activeId );\n\n        document.documentElement.style.height = \"\";\n        document.body.style.height = \"\";\n        document.body.style.overflow = \"\";\n\n        // Remove style values from the root and step elements\n        // Note: We remove the ones set by impress.js core. Otoh, we didn't preserve any original\n        // values. A more sophisticated implementation could keep track of original values and then\n        // reset those.\n        var steps = root.querySelectorAll( \".step\" );\n        for ( var i = 0; i < steps.length; i++ ) {\n            steps[ i ].classList.remove( \"future\" );\n            steps[ i ].classList.remove( \"past\" );\n            steps[ i ].classList.remove( \"present\" );\n            steps[ i ].classList.remove( \"active\" );\n            steps[ i ].style.position = \"\";\n            steps[ i ].style.transform = \"\";\n            steps[ i ].style[ \"transform-style\" ] = \"\";\n        }\n        root.style.position = \"\";\n        root.style[ \"transform-origin\" ] = \"\";\n        root.style.transition = \"\";\n        root.style[ \"transform-style\" ] = \"\";\n        root.style.top = \"\";\n        root.style.left = \"\";\n        root.style.transform = \"\";\n\n        // Reset id of steps (\"step-1\" id's are auto generated)\n        steps = startingState.roots[ rootId ].steps;\n        var step;\n        while ( step = steps.pop() ) {\n            if ( step.id === null ) {\n                step.el.removeAttribute( \"id\" );\n            } else {\n                step.el.setAttribute( \"id\", step.id );\n            }\n        }\n        delete startingState.roots[ rootId ];\n\n        // Move step div elements away from canvas, then delete canvas\n        // Note: There's an implicit assumption here that the canvas div is the only child element\n        // of the root div. If there would be something else, it's gonna be lost.\n        var canvas = root.firstChild;\n        var canvasHTML = canvas.innerHTML;\n        root.innerHTML = canvasHTML;\n\n        if ( roots[ rootId ] !== undefined ) {\n            delete roots[ rootId ];\n            rootsCount--;\n        }\n        if ( rootsCount === 0 ) {\n\n            // In the rare case that more than one impress root elements were initialized, these\n            // are only reset when all are uninitialized.\n            document.body.classList.remove( \"impress-supported\" );\n            if ( startingState.body.impressNotSupported ) {\n                document.body.classList.add( \"impress-not-supported\" );\n            }\n\n            // We need to remove or reset the meta element inserted by impress.js\n            var metas = document.head.querySelectorAll( \"meta\" );\n            for ( i = 0; i < metas.length; i++ ) {\n                var m = metas[ i ];\n                if ( m.name === \"viewport\" ) {\n                    if ( startingState.meta !== undefined ) {\n                        m.content = startingState.meta;\n                    } else {\n                        m.parentElement.removeChild( m );\n                    }\n                }\n            }\n        }\n\n    };\n\n} )( document, window );\n\n/**\n * Common utility functions\n *\n * Copyright 2011-2012 Bartek Szopka (@bartaz)\n * Henrik Ingo (c) 2016\n * MIT License\n */\n\n( function( document, window ) {\n    \"use strict\";\n    var roots = [];\n\n    var libraryFactory = function( rootId ) {\n        if ( roots[ rootId ] ) {\n            return roots[ rootId ];\n        }\n\n        // `$` returns first element for given CSS `selector` in the `context` of\n        // the given element or whole document.\n        var $ = function( selector, context ) {\n            context = context || document;\n            return context.querySelector( selector );\n        };\n\n        // `$$` return an array of elements for given CSS `selector` in the `context` of\n        // the given element or whole document.\n        var $$ = function( selector, context ) {\n            context = context || document;\n            return arrayify( context.querySelectorAll( selector ) );\n        };\n\n        // `arrayify` takes an array-like object and turns it into real Array\n        // to make all the Array.prototype goodness available.\n        var arrayify = function( a ) {\n            return [].slice.call( a );\n        };\n\n        // `byId` returns element with given `id` - you probably have guessed that ;)\n        var byId = function( id ) {\n            return document.getElementById( id );\n        };\n\n        // `getElementFromHash` returns an element located by id from hash part of\n        // window location.\n        var getElementFromHash = function() {\n\n            // Get id from url # by removing `#` or `#/` from the beginning,\n            // so both \"fallback\" `#slide-id` and \"enhanced\" `#/slide-id` will work\n            var encoded = window.location.hash.replace( /^#\\/?/, \"\" );\n            return byId( decodeURIComponent( encoded ) );\n        };\n\n        // `getUrlParamValue` return a given URL parameter value if it exists\n        // `undefined` if it doesn't exist\n        var getUrlParamValue = function( parameter ) {\n            var chunk = window.location.search.split( parameter + \"=\" )[ 1 ];\n            var value = chunk && chunk.split( \"&\" )[ 0 ];\n\n            if ( value !== \"\" ) {\n                return value;\n            }\n        };\n\n        // Throttling function calls, by Remy Sharp\n        // http://remysharp.com/2010/07/21/throttling-function-calls/\n        var throttle = function( fn, delay ) {\n            var timer = null;\n            return function() {\n                var context = this, args = arguments;\n                window.clearTimeout( timer );\n                timer = window.setTimeout( function() {\n                    fn.apply( context, args );\n                }, delay );\n            };\n        };\n\n        // `toNumber` takes a value given as `numeric` parameter and tries to turn\n        // it into a number. If it is not possible it returns 0 (or other value\n        // given as `fallback`).\n        var toNumber = function( numeric, fallback ) {\n            return isNaN( numeric ) ? ( fallback || 0 ) : Number( numeric );\n        };\n\n        /**\n         * Extends toNumber() to correctly compute also relative-to-screen-size values 5w and 5h.\n         *\n         * Returns the computed value in pixels with w/h postfix removed.\n         */\n        var toNumberAdvanced = function( numeric, fallback ) {\n            if ( typeof numeric !== \"string\" ) {\n                return toNumber( numeric, fallback );\n            }\n            var ratio = numeric.match( /^([+-]*[\\d\\.]+)([wh])$/ );\n            if ( ratio == null ) {\n                return toNumber( numeric, fallback );\n            } else {\n                var value = parseFloat( ratio[ 1 ] );\n                var config = window.impress.getConfig();\n                var multiplier = ratio[ 2 ] === \"w\" ? config.width : config.height;\n                return value * multiplier;\n            }\n        };\n\n        // `triggerEvent` builds a custom DOM event with given `eventName` and `detail` data\n        // and triggers it on element given as `el`.\n        var triggerEvent = function( el, eventName, detail ) {\n            var event = document.createEvent( \"CustomEvent\" );\n            event.initCustomEvent( eventName, true, true, detail );\n            el.dispatchEvent( event );\n        };\n\n        var lib = {\n            $: $,\n            $$: $$,\n            arrayify: arrayify,\n            byId: byId,\n            getElementFromHash: getElementFromHash,\n            throttle: throttle,\n            toNumber: toNumber,\n            toNumberAdvanced: toNumberAdvanced,\n            triggerEvent: triggerEvent,\n            getUrlParamValue: getUrlParamValue\n        };\n        roots[ rootId ] = lib;\n        return lib;\n    };\n\n    // Let impress core know about the existence of this library\n    window.impress.addLibraryFactory( { util: libraryFactory } );\n\n} )( document, window );\n\n/**\n * Helper functions for rotation.\n *\n * Tommy Tam (c) 2021\n * MIT License\n */\n( function( document, window ) {\n    \"use strict\";\n\n    // Singleton library variables\n    var roots = [];\n\n    var libraryFactory = function( rootId ) {\n        if ( roots[ \"impress-root-\" + rootId ] ) {\n            return roots[ \"impress-root-\" + rootId ];\n        }\n\n        /**\n         * Round the number to 2 decimals, it's enough for use\n         */\n        var roundNumber = function( num ) {\n            return Math.round( ( num + Number.EPSILON ) * 100 ) / 100;\n        };\n\n        /**\n         * Get the length/norm of a vector.\n         *\n         * https://en.wikipedia.org/wiki/Norm_(mathematics)\n         */\n        var vectorLength = function( vec ) {\n            return Math.sqrt( vec.x * vec.x + vec.y * vec.y + vec.z * vec.z );\n        };\n\n        /**\n         * Dot product of two vectors.\n         *\n         * https://en.wikipedia.org/wiki/Dot_product\n         */\n        var vectorDotProd = function( vec1, vec2 ) {\n            return vec1.x * vec2.x + vec1.y * vec2.y + vec1.z * vec2.z;\n        };\n\n        /**\n         * Cross product of two vectors.\n         *\n         * https://en.wikipedia.org/wiki/Cross_product\n         */\n        var vectorCrossProd = function( vec1, vec2 ) {\n            return {\n                x: vec1.y * vec2.z - vec1.z * vec2.y,\n                y: vec1.z * vec2.x - vec1.x * vec2.z,\n                z: vec1.x * vec2.y - vec1.y * vec2.x\n            };\n        };\n\n        /**\n         * Determine wheter a vector is a zero vector\n         */\n        var isZeroVector = function( vec ) {\n            return !roundNumber( vec.x ) && !roundNumber( vec.y ) && !roundNumber( vec.z );\n        };\n\n        /**\n         * Scalar triple product of three vectors.\n         *\n         * It can be used to determine the handness of vectors.\n         *\n         * https://en.wikipedia.org/wiki/Triple_product#Scalar_triple_product\n         */\n        var tripleProduct = function( vec1, vec2, vec3 ) {\n            return vectorDotProd( vectorCrossProd( vec1, vec2 ), vec3 );\n        };\n\n        /**\n         * The world/absolute unit coordinates.\n         *\n         * This coordinate is used by browser to position objects.\n         * It will not be affected by object rotations.\n         * All relative positions will finally be converted to this\n         * coordinate to be used.\n         */\n        var worldUnitCoordinate = {\n            x: { x:1, y:0, z:0 },\n            y: { x:0, y:1, z:0 },\n            z: { x:0, y:0, z:1 }\n        };\n\n        /**\n         * Make quaternion from rotation axis and angle.\n         *\n         * q = [ cos(½θ), sin(½θ) axis ]\n         *\n         * If the angle is zero, returns the corresponded quaternion\n         * of axis.\n         *\n         * If the angle is not zero, returns the rotating quaternion\n         * which corresponds to rotation about the axis, by the angle θ.\n         *\n         * https://en.wikipedia.org/wiki/Quaternion\n         * https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation\n         */\n        var makeQuaternion = function( axis, theta = 0 ) {\n            var r = 0;\n            var t = 1;\n\n            if ( theta ) {\n                var radians = theta * Math.PI / 180;\n                r = Math.cos( radians / 2 );\n                t = Math.sin( radians / 2 ) / vectorLength( axis );\n            }\n\n            var q = [ r, axis.x * t, axis.y * t, axis.z * t ];\n\n            return q;\n        };\n\n        /**\n         * Extract vector from quaternion\n         */\n        var quaternionToVector = function( quaternion ) {\n            return {\n                x: roundNumber( quaternion[ 1 ] ),\n                y: roundNumber( quaternion[ 2 ] ),\n                z: roundNumber( quaternion[ 3 ] )\n            };\n        };\n\n        /**\n         * Returns the conjugate quaternion of a quaternion\n         *\n         * https://en.wikipedia.org/wiki/Quaternion#Conjugation,_the_norm,_and_reciprocal\n         */\n        var conjugateQuaternion = function( quaternion ) {\n            return [ quaternion[ 0 ], -quaternion[ 1 ], -quaternion[ 2 ], -quaternion[ 3 ] ];\n        };\n\n        /**\n         * Left multiple two quaternion.\n         *\n         * Is's used to combine two rotating quaternion into one.\n         */\n        var leftMulQuaternion = function( q1, q2 ) {\n            return [\n                ( q1[ 0 ] * q2[ 0 ] - q1[ 1 ] * q2[ 1 ] - q1[ 2 ] * q2[ 2 ] - q1[ 3 ] * q2[ 3 ] ),\n                ( q1[ 1 ] * q2[ 0 ] + q1[ 0 ] * q2[ 1 ] - q1[ 3 ] * q2[ 2 ] + q1[ 2 ] * q2[ 3 ] ),\n                ( q1[ 2 ] * q2[ 0 ] + q1[ 3 ] * q2[ 1 ] + q1[ 0 ] * q2[ 2 ] - q1[ 1 ] * q2[ 3 ] ),\n                ( q1[ 3 ] * q2[ 0 ] - q1[ 2 ] * q2[ 1 ] + q1[ 1 ] * q2[ 2 ] + q1[ 0 ] * q2[ 3 ] )\n            ];\n        };\n\n        /**\n         * Convert a rotation into a quaternion\n         */\n        var rotationToQuaternion = function( baseCoordinate, rotation ) {\n            var order = rotation.order ? rotation.order : \"xyz\";\n            var axes = order.split( \"\" );\n            var result = [ 1, 0, 0, 0 ];\n\n            for ( var i = 0; i < axes.length; i++ ) {\n                var deg = rotation[ axes[ i ] ];\n                if ( !deg || ( Math.abs( deg ) < 0.0001 ) ) {\n                    continue;\n                }\n\n                // All CSS rotation is based on the rotated coordinate\n                // So we need to calculate the rotated coordinate first\n                var coordinate = baseCoordinate;\n                if ( i > 0 ) {\n                    coordinate = {\n                        x: rotateByQuaternion( baseCoordinate.x, result ),\n                        y: rotateByQuaternion( baseCoordinate.y, result ),\n                        z: rotateByQuaternion( baseCoordinate.z, result )\n                    };\n                }\n\n                result = leftMulQuaternion(\n                    makeQuaternion( coordinate[ axes[ i ] ], deg ),\n                    result );\n\n            }\n\n            return result;\n        };\n\n        /**\n         * Rotate a vector by a quaternion.\n         */\n        var rotateByQuaternion = function( vec, quaternion ) {\n            var q = makeQuaternion( vec );\n\n            q = leftMulQuaternion(\n                leftMulQuaternion( quaternion, q ),\n                conjugateQuaternion( quaternion ) );\n\n            return quaternionToVector( q );\n        };\n\n        /**\n         * Rotate a vector by rotaion sequence.\n         */\n        var rotateVector = function( baseCoordinate, vec, rotation ) {\n            var quaternion = rotationToQuaternion( baseCoordinate, rotation );\n\n            return rotateByQuaternion( vec, quaternion );\n        };\n\n        /**\n         * Given a rotation, return the rotationed coordinate\n         */\n        var rotateCoordinate = function( coordinate, rotation ) {\n            var quaternion = rotationToQuaternion( coordinate, rotation );\n\n            return {\n                x: rotateByQuaternion( coordinate.x, quaternion ),\n                y: rotateByQuaternion( coordinate.y, quaternion ),\n                z: rotateByQuaternion( coordinate.z, quaternion )\n            };\n        };\n\n        /**\n         * Return the angle between two vector.\n         *\n         * The axis is used to determine the rotation direction.\n         */\n        var angleBetweenTwoVector = function( axis, vec1, vec2 ) {\n            var vecLen1 = vectorLength( vec1 );\n            var vecLen2 = vectorLength( vec2 );\n\n            if ( !vecLen1 || !vecLen2 ) {\n                return 0;\n            }\n\n            var cos = vectorDotProd( vec1, vec2 ) / vecLen1 / vecLen2 ;\n            var angle = Math.acos( cos ) * 180 / Math.PI;\n\n            if ( tripleProduct( vec1, vec2, axis ) > 0 ) {\n                return angle;\n            } else {\n                return -angle;\n            }\n        };\n\n        /**\n         * Return the angle between a vector and a plane.\n         *\n         * The plane is determined by an axis and a vector on the plane.\n         */\n        var angleBetweenPlaneAndVector = function( axis, planeVec, rotatedVec ) {\n            var norm = vectorCrossProd( axis, planeVec );\n\n            if ( isZeroVector( norm ) ) {\n                return 0;\n            }\n\n            return 90 - angleBetweenTwoVector( axis, rotatedVec, norm );\n        };\n\n        /**\n         * Calculated a order specified rotation sequence to\n         * transform from the world coordinate to required coordinate.\n         */\n        var coordinateToOrderedRotation = function( coordinate, order ) {\n            var axis0 = order[ 0 ];\n            var axis1 = order[ 1 ];\n            var axis2 = order[ 2 ];\n            var reversedOrder = order.split( \"\" ).reverse().join( \"\" );\n\n            var rotate2 = angleBetweenPlaneAndVector(\n                coordinate[ axis2 ],\n                worldUnitCoordinate[ axis0 ],\n                coordinate[ axis0 ] );\n\n            // The r2 is the reverse of rotate for axis2\n            // The coordinate1 is the coordinate before rotate of axis2\n            var r2 = { order: reversedOrder };\n            r2[ axis2 ] = -rotate2;\n\n            var coordinate1 = rotateCoordinate( coordinate, r2 );\n\n            // Calculate the rotation for axis1\n            var rotate1 = angleBetweenTwoVector(\n                coordinate1[ axis1 ],\n                worldUnitCoordinate[ axis0 ],\n                coordinate1[ axis0 ] );\n\n            // Calculate the rotation for axis0\n            var rotate0 = angleBetweenTwoVector(\n                worldUnitCoordinate[ axis0 ],\n                worldUnitCoordinate[ axis1 ],\n                coordinate1[ axis1 ] );\n\n            var rotation = { };\n            rotation.order = order;\n            rotation[ axis0 ] = roundNumber( rotate0 );\n            rotation[ axis1 ] = roundNumber( rotate1 );\n            rotation[ axis2 ] = roundNumber( rotate2 );\n\n            return rotation;\n        };\n\n        /**\n         * Returns the possible rotations from unit coordinate\n         * to specified coordinate.\n         */\n        var possibleRotations = function( coordinate ) {\n            var orders = [ \"xyz\", \"xzy\", \"yxz\", \"yzx\", \"zxy\", \"zyx\" ];\n            var rotations = [ ];\n\n            for ( var i = 0; i < orders.length; ++i ) {\n                rotations.push(\n                    coordinateToOrderedRotation( coordinate, orders[ i ] )\n                );\n            }\n\n            return rotations;\n        };\n\n        /**\n         * Calculate a degree which in range (-180, 180] of baseDeg\n         */\n        var nearestAngle = function( baseDeg, deg ) {\n            while ( deg > baseDeg + 180 ) {\n                deg -= 360;\n            }\n\n            while ( deg < baseDeg - 180 ) {\n                deg += 360;\n            }\n\n            return deg;\n        };\n\n        /**\n         * Given a base rotation and multiple rotations, return the best one.\n         *\n         * The best one is the one has least rotate from base.\n         */\n        var bestRotation = function( baseRotate, rotations ) {\n            var bestScore;\n            var bestRotation;\n\n            for ( var i = 0; i < rotations.length; ++i ) {\n                var rotation = {\n                    order: rotations[ i ].order,\n                    x: nearestAngle( baseRotate.x, rotations[ i ].x ),\n                    y: nearestAngle( baseRotate.y, rotations[ i ].y ),\n                    z: nearestAngle( baseRotate.z, rotations[ i ].z )\n                };\n\n                var score = Math.abs( rotation.x - baseRotate.x ) +\n                    Math.abs( rotation.y - baseRotate.y ) +\n                    Math.abs( rotation.z - baseRotate.z );\n\n                if ( !i || ( score < bestScore ) ) {\n                    bestScore = score;\n                    bestRotation = rotation;\n                }\n            }\n\n            return bestRotation;\n        };\n\n        /**\n         * Given a coordinate, return the best rotation to achieve it.\n         *\n         * The baseRotate is used to select the near rotation from it.\n         */\n        var coordinateToRotation = function( baseRotate, coordinate ) {\n            var rotations = possibleRotations( coordinate );\n\n            return bestRotation( baseRotate, rotations );\n        };\n\n        /**\n         * Apply a relative rotation to the base rotation.\n         *\n         * Calculate the coordinate after the rotation on each axis,\n         * and finally find out a one step rotation has the effect\n         * of two rotation.\n         *\n         * If there're multiple way to accomplish, select the one\n         * that is nearest to the base.\n         *\n         * Return one rotation has the same effect.\n         */\n        var combineRotations = function( rotations ) {\n\n            // No rotation\n            if ( rotations.length <= 0 ) {\n                return { x:0, y:0, z:0, order:\"xyz\" };\n            }\n\n            // Find out the base coordinate\n            var coordinate = worldUnitCoordinate;\n\n            // One by one apply rotations in order\n            for ( var i = 0; i < rotations.length; i++ ) {\n                coordinate = rotateCoordinate( coordinate, rotations[ i ] );\n            }\n\n            // Calculate one rotation from unit coordinate to rotated\n            // coordinate.  Because there're multiple possibles,\n            // select the one nearest to the base\n            var rotate = coordinateToRotation( rotations[ 0 ], coordinate );\n\n            return rotate;\n        };\n\n        var translateRelative = function( relative, prevRotation ) {\n            var result = rotateVector(\n                worldUnitCoordinate, relative, prevRotation );\n            result.rotate = combineRotations(\n                [ prevRotation, relative.rotate ] );\n\n            return result;\n        };\n\n        var lib = {\n            translateRelative: translateRelative\n        };\n\n        roots[ \"impress-root-\" + rootId ] = lib;\n        return lib;\n    };\n\n    // Let impress core know about the existence of this library\n    window.impress.addLibraryFactory( { rotation: libraryFactory } );\n\n} )( document, window );\n\n/**\n * Autoplay plugin - Automatically advance slideshow after N seconds\n *\n * Copyright 2016 Henrik Ingo, henrik.ingo@avoinelama.fi\n * Released under the MIT license.\n */\n/* global clearTimeout, setTimeout, document */\n\n( function( document ) {\n    \"use strict\";\n\n    var autoplayDefault = 0;\n    var currentStepTimeout = 0;\n    var api = null;\n    var timeoutHandle = null;\n    var root = null;\n    var util;\n\n    // On impress:init, check whether there is a default setting, as well as\n    // handle step-1.\n    document.addEventListener( \"impress:init\", function( event ) {\n        util = event.detail.api.lib.util;\n\n        // Getting API from event data instead of global impress().init().\n        // You don't even need to know what is the id of the root element\n        // or anything. `impress:init` event data gives you everything you\n        // need to control the presentation that was just initialized.\n        api = event.detail.api;\n        root = event.target;\n\n        // Element attributes starting with \"data-\", become available under\n        // element.dataset. In addition hyphenized words become camelCased.\n        var data = root.dataset;\n        var autoplay = util.getUrlParamValue( \"impress-autoplay\" ) || data.autoplay;\n\n        if ( autoplay ) {\n            autoplayDefault = util.toNumber( autoplay, 0 );\n        }\n\n        var toolbar = document.querySelector( \"#impress-toolbar\" );\n        if ( toolbar ) {\n            addToolbarButton( toolbar );\n        }\n\n        api.lib.gc.pushCallback( function() {\n            clearTimeout( timeoutHandle );\n        } );\n\n        // Note that right after impress:init event, also impress:stepenter is\n        // triggered for the first slide, so that's where code flow continues.\n    }, false );\n\n    document.addEventListener( \"impress:autoplay:pause\", function( event ) {\n        status = \"paused\";\n        reloadTimeout( event );\n    }, false );\n\n    document.addEventListener( \"impress:autoplay:play\", function( event ) {\n        status = \"playing\";\n        reloadTimeout( event );\n    }, false );\n\n    // If default autoplay time was defined in the presentation root, or\n    // in this step, set timeout.\n    var reloadTimeout = function( event ) {\n        var step = event.target;\n        currentStepTimeout = util.toNumber( step.dataset.autoplay, autoplayDefault );\n        if ( status === \"paused\" ) {\n            setAutoplayTimeout( 0 );\n        } else {\n            setAutoplayTimeout( currentStepTimeout );\n        }\n    };\n\n    document.addEventListener( \"impress:stepenter\", function( event ) {\n        reloadTimeout( event );\n    }, false );\n\n    document.addEventListener( \"impress:substep:enter\", function( event ) {\n        reloadTimeout( event );\n    }, false );\n\n    /**\n     * Set timeout after which we move to next() step.\n     */\n    var setAutoplayTimeout = function( timeout ) {\n        if ( timeoutHandle ) {\n            clearTimeout( timeoutHandle );\n        }\n\n        if ( timeout > 0 ) {\n            timeoutHandle = setTimeout( function() { api.next(); }, timeout * 1000 );\n        }\n        setButtonText();\n    };\n\n    /*** Toolbar plugin integration *******************************************/\n    var status = \"not clicked\";\n    var toolbarButton = null;\n\n    var makeDomElement = function( html ) {\n        var tempDiv = document.createElement( \"div\" );\n        tempDiv.innerHTML = html;\n        return tempDiv.firstChild;\n    };\n\n    var toggleStatus = function() {\n        if ( currentStepTimeout > 0 && status !== \"paused\" ) {\n            status = \"paused\";\n        } else {\n            status = \"playing\";\n        }\n    };\n\n    var getButtonText = function() {\n        if ( currentStepTimeout > 0 && status !== \"paused\" ) {\n            return \"||\"; // Pause\n        } else {\n            return \"&#9654;\"; // Play\n        }\n    };\n\n    var setButtonText = function() {\n        if ( toolbarButton ) {\n\n            // Keep button size the same even if label content is changing\n            var buttonWidth = toolbarButton.offsetWidth;\n            var buttonHeight = toolbarButton.offsetHeight;\n            toolbarButton.innerHTML = getButtonText();\n            if ( !toolbarButton.style.width ) {\n                toolbarButton.style.width = buttonWidth + \"px\";\n            }\n            if ( !toolbarButton.style.height ) {\n                toolbarButton.style.height = buttonHeight + \"px\";\n            }\n        }\n    };\n\n    var addToolbarButton = function( toolbar ) {\n        var html = '<button id=\"impress-autoplay-playpause\" ' + // jshint ignore:line\n                   'title=\"Autoplay\" class=\"impress-autoplay\">' + // jshint ignore:line\n                   getButtonText() + \"</button>\"; // jshint ignore:line\n        toolbarButton = makeDomElement( html );\n        toolbarButton.addEventListener( \"click\", function() {\n            toggleStatus();\n            if ( status === \"playing\" ) {\n                if ( autoplayDefault === 0 ) {\n                    autoplayDefault = 7;\n                }\n                if ( currentStepTimeout === 0 ) {\n                    currentStepTimeout = autoplayDefault;\n                }\n                setAutoplayTimeout( currentStepTimeout );\n            } else if ( status === \"paused\" ) {\n                setAutoplayTimeout( 0 );\n            }\n        } );\n\n        util.triggerEvent( toolbar, \"impress:toolbar:appendChild\",\n                      { group: 10, element: toolbarButton } );\n    };\n\n} )( document );\n\n/**\n * Blackout plugin\n *\n * Press b or . to hide all slides, and b or . again to show them.\n * Also navigating to a different slide will show them again (impress:stepleave).\n *\n * Copyright 2014 @Strikeskids\n * Released under the MIT license.\n */\n/* global document */\n\n( function( document ) {\n    \"use strict\";\n\n    var canvas = null;\n    var blackedOut = false;\n    var util = null;\n    var root = null;\n    var api = null;\n\n    // While waiting for a shared library of utilities, copying these 2 from main impress.js\n    var css = function( el, props ) {\n        var key, pkey;\n        for ( key in props ) {\n            if ( props.hasOwnProperty( key ) ) {\n                pkey = pfx( key );\n                if ( pkey !== null ) {\n                    el.style[ pkey ] = props[ key ];\n                }\n            }\n        }\n        return el;\n    };\n\n    var pfx = ( function() {\n\n        var style = document.createElement( \"dummy\" ).style,\n            prefixes = \"Webkit Moz O ms Khtml\".split( \" \" ),\n            memory = {};\n\n        return function( prop ) {\n            if ( typeof memory[ prop ] === \"undefined\" ) {\n\n                var ucProp  = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ),\n                    props   = ( prop + \" \" + prefixes.join( ucProp + \" \" ) + ucProp ).split( \" \" );\n\n                memory[ prop ] = null;\n                for ( var i in props ) {\n                    if ( style[ props[ i ] ] !== undefined ) {\n                        memory[ prop ] = props[ i ];\n                        break;\n                    }\n                }\n\n            }\n\n            return memory[ prop ];\n        };\n\n    } )();\n\n    var removeBlackout = function() {\n        if ( blackedOut ) {\n            css( canvas, {\n                display: \"block\"\n            } );\n            blackedOut = false;\n            util.triggerEvent( root, \"impress:autoplay:play\", {} );\n        }\n    };\n\n    var blackout = function() {\n        if ( blackedOut ) {\n            removeBlackout();\n        } else {\n            css( canvas, {\n                display: ( blackedOut = !blackedOut ) ? \"none\" : \"block\"\n            } );\n            blackedOut = true;\n            util.triggerEvent( root, \"impress:autoplay:pause\", {} );\n        }\n    };\n\n    // Wait for impress.js to be initialized\n    document.addEventListener( \"impress:init\", function( event ) {\n        api = event.detail.api;\n        util = api.lib.util;\n        root = event.target;\n        canvas = root.firstElementChild;\n        var gc = api.lib.gc;\n\n        gc.addEventListener( document, \"keydown\", function( event ) {\n\n            // Accept b or . -> . is sent by presentation remote controllers\n            if ( event.keyCode === 66 || event.keyCode === 190 ) {\n                event.preventDefault();\n                if ( !blackedOut ) {\n                    blackout();\n                } else {\n                    removeBlackout();\n                }\n            }\n        }, false );\n\n        gc.addEventListener( document, \"keyup\", function( event ) {\n\n            // Accept b or . -> . is sent by presentation remote controllers\n            if ( event.keyCode === 66 || event.keyCode === 190 ) {\n                event.preventDefault();\n            }\n        }, false );\n\n    }, false );\n\n    document.addEventListener( \"impress:stepleave\", function() {\n        removeBlackout();\n    }, false );\n\n} )( document );\n\n\n/**\n * Extras Plugin\n *\n * This plugin performs initialization (like calling mermaid.initialize())\n * for the extras/ plugins if they are loaded into a presentation.\n *\n * See README.md for details.\n *\n * Copyright 2016 Henrik Ingo (@henrikingo)\n * Released under the MIT license.\n */\n/* global markdown, marked, hljs, mermaid, impress */\n\n( function( document, window ) {\n    \"use strict\";\n\n    const SLIDE_SEPARATOR = /^-----$/m;\n\n    const getMarkdownParser = function( ) {\n        if ( window.hasOwnProperty( \"marked\" ) ) {\n\n            // Using marked\n            return function( elem, src ) {\n                return marked.parse( src );\n            };\n        } else if ( window.hasOwnProperty( \"markdown\" ) ) {\n\n            // Using builtin markdown engine\n            return function( elem, src ) {\n                var dialect = elem.dataset.markdownDialect;\n                return markdown.toHTML( src, dialect );\n            };\n        }\n\n        return null;\n    };\n\n    const getMarkdownSlides = function( elem ) {\n        var text = elem.textContent;\n\n        // Using first not blank line to detect leading whitespaces.\n        // can't properly handle the mixing of space and tabs\n        var m = text.match( /^([ \\t]*)\\S/m );\n        if ( m !== null ) {\n            text = text.replace( new RegExp( \"^\" + m[ 1 ], \"mg\" ), \"\" );\n        }\n\n        return text.split( SLIDE_SEPARATOR );\n    };\n\n    const convertMarkdowns = function( selector ) {\n\n        // Detect markdown engine\n        var parseMarkdown = getMarkdownParser();\n        if ( !parseMarkdown ) {\n            return;\n        }\n\n        for ( var elem of document.querySelectorAll( selector ) ) {\n            var id = null;\n            if ( elem.id ) {\n                id = elem.id;\n                elem.id = \"\";\n            }\n\n            var origTitle = null;\n            if ( elem.title ) {\n                origTitle = elem.title;\n                elem.title = \"\";\n            }\n\n            var slides = getMarkdownSlides( elem );\n            var slideElems = [ elem ];\n\n            for ( var j = 1; j < slides.length; ++j ) {\n                var newElem = elem.cloneNode( false );\n                newElem.id = \"\";\n                elem.parentNode.insertBefore( newElem, slideElems[ 0 ] );\n                slideElems.splice( 0, 0, newElem );\n            }\n\n            if ( id ) {\n                slideElems[ 0 ].id = id;\n            }\n\n            for ( var i = 0; i < slides.length; ++i ) {\n                slideElems[ i ].innerHTML =\n                    parseMarkdown( slideElems[ i ], slides[ i ] );\n\n                if ( origTitle && ( i === 0 ) ) {\n                    slideElems[ i ].title = origTitle;\n                }\n            }\n        }\n    };\n\n    var preInit = function() {\n\n        // Query all .markdown elements and translate to HTML\n        convertMarkdowns( \".markdown\" );\n\n        if ( window.hljs ) {\n            hljs.initHighlightingOnLoad();\n        }\n\n        if ( window.mermaid ) {\n            mermaid.initialize( { startOnLoad:true } );\n        }\n    };\n\n    // Register the plugin to be called in pre-init phase\n    // Note: Markdown.js should run early/first, because it creates new div elements.\n    // So add this with a lower-than-default weight.\n    impress.addPreInitPlugin( preInit, 1 );\n\n} )( document, window );\n\n\n/**\n * Form support\n *\n * Functionality to better support use of input, textarea, button... elements in a presentation.\n *\n * This plugin does two things:\n *\n * Set stopPropagation on any element that might take text input. This allows users to type, for\n * example, the letter 'P' into a form field, without causing the presenter console to spring up.\n *\n * On impress:stepleave, de-focus any potentially active\n * element. This is to prevent the focus from being left in a form element that is no longer visible\n * in the window, and user therefore typing garbage into the form.\n *\n * TODO: Currently it is not possible to use TAB to navigate between form elements. Impress.js, and\n * in particular the navigation plugin, unfortunately must fully take control of the tab key,\n * otherwise a user could cause the browser to scroll to a link or button that's not on the current\n * step. However, it could be possible to allow tab navigation between form elements, as long as\n * they are on the active step. This is a topic for further study.\n *\n * Copyright 2016 Henrik Ingo\n * MIT License\n */\n/* global document */\n( function( document ) {\n    \"use strict\";\n    var root;\n    var api;\n\n    document.addEventListener( \"impress:init\", function( event ) {\n        root = event.target;\n        api = event.detail.api;\n        var gc = api.lib.gc;\n\n        var selectors = [ \"input\", \"textarea\", \"select\", \"[contenteditable=true]\" ];\n        for ( var selector of selectors ) {\n            var elements = document.querySelectorAll( selector );\n            if ( !elements ) {\n                continue;\n            }\n\n            for ( var i = 0; i < elements.length; i++ ) {\n                var e = elements[ i ];\n                gc.addEventListener( e, \"keydown\", function( event ) {\n                    event.stopPropagation();\n                } );\n                gc.addEventListener( e, \"keyup\", function( event ) {\n                    event.stopPropagation();\n                } );\n            }\n        }\n    }, false );\n\n    document.addEventListener( \"impress:stepleave\", function() {\n        document.activeElement.blur();\n    }, false );\n\n} )( document );\n\n\n/**\n * Fullscreen plugin\n *\n * Press F5 to enter fullscreen and ESC to exit fullscreen mode.\n *\n * Copyright 2019 @giflw\n * Released under the MIT license.\n */\n/* global document */\n\n( function( document ) {\n    \"use strict\";\n\n    function enterFullscreen() {\n        var elem = document.documentElement;\n        if ( !document.fullscreenElement ) {\n            elem.requestFullscreen();\n        }\n    }\n\n    function exitFullscreen() {\n        if ( document.fullscreenElement ) {\n            document.exitFullscreen();\n        }\n    }\n\n    // Wait for impress.js to be initialized\n    document.addEventListener( \"impress:init\", function( event ) {\n        var api = event.detail.api;\n        var root = event.target;\n        var gc = api.lib.gc;\n        var util = api.lib.util;\n\n        gc.addEventListener( document, \"keydown\", function( event ) {\n\n            // 116 (F5) is sent by presentation remote controllers\n            if ( event.code === \"F5\" ) {\n                event.preventDefault();\n                enterFullscreen();\n                util.triggerEvent( root.querySelector( \".active\" ), \"impress:steprefresh\" );\n            }\n\n            // 27 (Escape) is sent by presentation remote controllers\n            if ( event.key === \"Escape\" || event.key === \"F5\" ) {\n                event.preventDefault();\n                exitFullscreen();\n                util.triggerEvent( root.querySelector( \".active\" ), \"impress:steprefresh\" );\n            }\n        }, false );\n\n        util.triggerEvent( document, \"impress:help:add\",\n            { command: \"F5 / ESC\", text: \"Fullscreen: Enter / Exit\", row: 200 } );\n\n    }, false );\n\n} )( document );\n\n\n/**\n * Goto Plugin\n *\n * The goto plugin is a pre-stepleave plugin. It is executed before impress:stepleave,\n * and will alter the destination where to transition next.\n *\n * Example:\n *\n *         <!-- When leaving this step, go directly to \"step-5\" -->\n *         <div class=\"step\" data-goto=\"step-5\">\n *\n *         <!-- When leaving this step with next(), go directly to \"step-5\", instead of next step.\n *              If moving backwards to previous step - e.g. prev() instead of next() -\n *              then go to \"step-1\". -->\n *         <div class=\"step\" data-goto-next=\"step-5\" data-goto-prev=\"step-1\">\n *\n *        <!-- data-goto-key-list and data-goto-next-list allow you to build advanced non-linear\n *             navigation. -->\n *        <div class=\"step\"\n *             data-goto-key-list=\"ArrowUp ArrowDown ArrowRight ArrowLeft\"\n *             data-goto-next-list=\"step-4 step-3 step-2 step-5\">\n *\n * See https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values for a table\n * of what strings to use for each key.\n *\n * Copyright 2016-2017 Henrik Ingo (@henrikingo)\n * Released under the MIT license.\n */\n/* global window, document, impress */\n\n( function( document, window ) {\n    \"use strict\";\n    var lib;\n\n    document.addEventListener( \"impress:init\", function( event ) {\n        lib = event.detail.api.lib;\n    }, false );\n\n    var isNumber = function( numeric ) {\n        return !isNaN( numeric );\n    };\n\n    var goto = function( event ) {\n        if ( ( !event ) || ( !event.target ) ) {\n            return;\n        }\n\n        var data = event.target.dataset;\n        var steps = document.querySelectorAll( \".step\" );\n\n        // Data-goto-key-list=\"\" & data-goto-next-list=\"\" //////////////////////////////////////////\n        if ( data.gotoKeyList !== undefined &&\n             data.gotoNextList !== undefined &&\n             event.origEvent !== undefined &&\n             event.origEvent.key !== undefined ) {\n            var keylist = data.gotoKeyList.split( \" \" );\n            var nextlist = data.gotoNextList.split( \" \" );\n\n            if ( keylist.length !== nextlist.length ) {\n                window.console.log(\n                    \"impress goto plugin: data-goto-key-list and data-goto-next-list don't match:\"\n                );\n                window.console.log( keylist );\n                window.console.log( nextlist );\n\n                // Don't return, allow the other categories to work despite this error\n            } else {\n                var index = keylist.indexOf( event.origEvent.key );\n                if ( index >= 0 ) {\n                    var next = nextlist[ index ];\n                    if ( isNumber( next ) ) {\n                        event.detail.next = steps[ next ];\n\n                        // If the new next element has its own transitionDuration, we're responsible\n                        // for setting that on the event as well\n                        event.detail.transitionDuration = lib.util.toNumber(\n                            event.detail.next.dataset.transitionDuration,\n                            event.detail.transitionDuration\n                        );\n                        return;\n                    } else {\n                        var newTarget = document.getElementById( next );\n                        if ( newTarget && newTarget.classList.contains( \"step\" ) ) {\n                            event.detail.next = newTarget;\n                            event.detail.transitionDuration = lib.util.toNumber(\n                                event.detail.next.dataset.transitionDuration,\n                                event.detail.transitionDuration\n                            );\n                            return;\n                        } else {\n                            window.console.log( \"impress goto plugin: \" + next +\n                                                \" is not a step in this impress presentation.\" );\n                        }\n                    }\n                }\n            }\n        }\n\n        // Data-goto-next=\"\" & data-goto-prev=\"\" ///////////////////////////////////////////////////\n\n        // Handle event.target data-goto-next attribute\n        if ( isNumber( data.gotoNext ) && event.detail.reason === \"next\" ) {\n            event.detail.next = steps[ data.gotoNext ];\n\n            // If the new next element has its own transitionDuration, we're responsible for setting\n            // that on the event as well\n            event.detail.transitionDuration = lib.util.toNumber(\n                event.detail.next.dataset.transitionDuration, event.detail.transitionDuration\n            );\n            return;\n        }\n        if ( data.gotoNext && event.detail.reason === \"next\" ) {\n            var newTarget = document.getElementById( data.gotoNext ); // jshint ignore:line\n            if ( newTarget && newTarget.classList.contains( \"step\" ) ) {\n                event.detail.next = newTarget;\n                event.detail.transitionDuration = lib.util.toNumber(\n                    event.detail.next.dataset.transitionDuration,\n                    event.detail.transitionDuration\n                );\n                return;\n            } else {\n                window.console.log( \"impress goto plugin: \" + data.gotoNext +\n                                    \" is not a step in this impress presentation.\" );\n            }\n        }\n\n        // Handle event.target data-goto-prev attribute\n        if ( isNumber( data.gotoPrev ) && event.detail.reason === \"prev\" ) {\n            event.detail.next = steps[ data.gotoPrev ];\n            event.detail.transitionDuration = lib.util.toNumber(\n                event.detail.next.dataset.transitionDuration, event.detail.transitionDuration\n            );\n            return;\n        }\n        if ( data.gotoPrev && event.detail.reason === \"prev\" ) {\n            var newTarget = document.getElementById( data.gotoPrev ); // jshint ignore:line\n            if ( newTarget && newTarget.classList.contains( \"step\" ) ) {\n                event.detail.next = newTarget;\n                event.detail.transitionDuration = lib.util.toNumber(\n                    event.detail.next.dataset.transitionDuration, event.detail.transitionDuration\n                );\n                return;\n            } else {\n                window.console.log( \"impress goto plugin: \" + data.gotoPrev +\n                                    \" is not a step in this impress presentation.\" );\n            }\n        }\n\n        // Data-goto=\"\" ///////////////////////////////////////////////////////////////////////////\n\n        // Handle event.target data-goto attribute\n        if ( isNumber( data.goto ) ) {\n            event.detail.next = steps[ data.goto ];\n            event.detail.transitionDuration = lib.util.toNumber(\n                event.detail.next.dataset.transitionDuration, event.detail.transitionDuration\n            );\n            return;\n        }\n        if ( data.goto ) {\n            var newTarget = document.getElementById( data.goto ); // jshint ignore:line\n            if ( newTarget && newTarget.classList.contains( \"step\" ) ) {\n                event.detail.next = newTarget;\n                event.detail.transitionDuration = lib.util.toNumber(\n                    event.detail.next.dataset.transitionDuration, event.detail.transitionDuration\n                );\n                return;\n            } else {\n                window.console.log( \"impress goto plugin: \" + data.goto +\n                                    \" is not a step in this impress presentation.\" );\n            }\n        }\n    };\n\n    // Register the plugin to be called in pre-stepleave phase\n    impress.addPreStepLeavePlugin( goto );\n\n} )( document, window );\n\n\n/**\n * Help popup plugin\n *\n * Example:\n *\n *     <!-- Show a help popup at start, or if user presses \"H\" -->\n *     <div id=\"impress-help\"></div>\n *\n * For developers:\n *\n * Typical use for this plugin, is for plugins that support some keypress, to add a line\n * to the help popup produced by this plugin. For example \"P: Presenter console\".\n *\n * Copyright 2016 Henrik Ingo (@henrikingo)\n * Released under the MIT license.\n */\n/* global window, document */\n\n( function( document, window ) {\n    \"use strict\";\n    var rows = [];\n    var timeoutHandle;\n\n    var triggerEvent = function( el, eventName, detail ) {\n        var event = document.createEvent( \"CustomEvent\" );\n        event.initCustomEvent( eventName, true, true, detail );\n        el.dispatchEvent( event );\n    };\n\n    var renderHelpDiv = function() {\n        var helpDiv = document.getElementById( \"impress-help\" );\n        if ( helpDiv ) {\n            var html = [];\n            for ( var row in rows ) {\n                for ( var arrayItem in row ) {\n                    html.push( rows[ row ][ arrayItem ] );\n                }\n            }\n            if ( html ) {\n                helpDiv.innerHTML = \"<table>\\n\" + html.join( \"\\n\" ) + \"</table>\\n\";\n            }\n        }\n    };\n\n    var toggleHelp = function() {\n        var helpDiv = document.getElementById( \"impress-help\" );\n        if ( !helpDiv ) {\n            return;\n        }\n\n        if ( helpDiv.style.display === \"block\" ) {\n            helpDiv.style.display = \"none\";\n        } else {\n            helpDiv.style.display = \"block\";\n            window.clearTimeout( timeoutHandle );\n        }\n    };\n\n    document.addEventListener( \"keyup\", function( event ) {\n\n        if ( event.keyCode === 72 || event.keyCode === 191 ) { // \"h\" || \"?\"\n            event.preventDefault();\n            toggleHelp();\n        }\n    }, false );\n\n    // API\n    // Other plugins can add help texts, typically if they support an action on a keypress.\n    /**\n     * Add a help text to the help popup.\n     *\n     * :param: e.detail.command  Example: \"H\"\n     * :param: e.detail.text     Example: \"Show this help.\"\n     * :param: e.detail.row      Row index from 0 to 9 where to place this help text. Example: 0\n     */\n    document.addEventListener( \"impress:help:add\", function( e ) {\n\n        // The idea is for the sender of the event to supply a unique row index, used for sorting.\n        // But just in case two plugins would ever use the same row index, we wrap each row into\n        // its own array. If there are more than one entry for the same index, they are shown in\n        // first come, first serve ordering.\n        var rowIndex = e.detail.row;\n        if ( typeof rows[ rowIndex ] !== \"object\" || !rows[ rowIndex ].isArray ) {\n            rows[ rowIndex ] = [];\n        }\n        rows[ e.detail.row ].push( \"<tr><td><strong>\" + e.detail.command + \"</strong></td><td>\" +\n                                   e.detail.text + \"</td></tr>\" );\n        renderHelpDiv();\n    } );\n\n    document.addEventListener( \"impress:init\", function( e ) {\n        renderHelpDiv();\n\n        // At start, show the help for 7 seconds.\n        var helpDiv = document.getElementById( \"impress-help\" );\n        if ( helpDiv ) {\n            helpDiv.style.display = \"block\";\n            timeoutHandle = window.setTimeout( function() {\n                var helpDiv = document.getElementById( \"impress-help\" );\n                helpDiv.style.display = \"none\";\n            }, 7000 );\n\n            // Regster callback to empty the help div on teardown\n            var api = e.detail.api;\n            api.lib.gc.pushCallback( function() {\n                window.clearTimeout( timeoutHandle );\n                helpDiv.style.display = \"\";\n                helpDiv.innerHTML = \"\";\n                rows = [];\n            } );\n        }\n\n        // Use our own API to register the help text for \"h\"\n        triggerEvent( document, \"impress:help:add\",\n                      { command: \"H\", text: \"Show this help\", row: 0 } );\n    } );\n\n} )( document, window );\n\n\n/**\n * Adds a presenter console to impress.js\n *\n * MIT Licensed, see license.txt.\n *\n * Copyright 2012, 2013, 2015 impress-console contributors (see README.txt)\n *\n * version: 1.3-dev\n *\n */\n\n// This file contains so much HTML, that we will just respectfully disagree about js\n/* jshint quotmark:single */\n/* global navigator, top, setInterval, clearInterval, document, window */\n\n( function( document, window ) {\n    'use strict';\n\n    // TODO: Move this to src/lib/util.js\n    var triggerEvent = function( el, eventName, detail ) {\n        var event = document.createEvent( 'CustomEvent' );\n        event.initCustomEvent( eventName, true, true, detail );\n        el.dispatchEvent( event );\n    };\n\n    // Create Language object depending on browsers language setting\n    var lang;\n    switch ( navigator.language ) {\n    case 'de':\n        lang = {\n            'noNotes': '<div class=\"noNotes\">Keine Notizen hierzu</div>',\n            'restart': 'Neustart',\n            'clickToOpen': 'Klicken um Sprecherkonsole zu öffnen',\n            'prev': 'zurück',\n            'next': 'weiter',\n            'loading': 'initalisiere',\n            'ready': 'Bereit',\n            'moving': 'in Bewegung',\n            'useAMPM': false\n        };\n        break;\n    case 'zh-CN':\n    case 'zh-cn':\n        lang = {\n            'noNotes': '<div class=\"noNotes\">当前帧没有备注</div>',\n            'restart': '重新开始',\n            'clickToOpen': '点击以打开演讲者控制界面',\n            'prev': '上一帧',\n            'next': '下一帧',\n            'loading': '加载中',\n            'ready': '就绪',\n            'moving': '移动中',\n            'useAMPM': false\n        };\n        break;\n    case 'en': // jshint ignore:line\n    default : // jshint ignore:line\n        lang = {\n            'noNotes': '<div class=\"noNotes\">No notes for this step</div>',\n            'restart': 'Restart',\n            'clickToOpen': 'Click to open speaker console',\n            'prev': 'Prev',\n            'next': 'Next',\n            'loading': 'Loading',\n            'ready': 'Ready',\n            'moving': 'Moving',\n            'useAMPM': false\n        };\n        break;\n    }\n\n    // Settings to set iframe in speaker console\n    const preViewDefaultFactor = 0.7;\n    const preViewMinimumFactor = 0.5;\n    const preViewGap    = 4;\n\n    // This is the default template for the speaker console window\n    const consoleTemplate = '<!DOCTYPE html>' +\n        '<html id=\"impressconsole\"><head>' +\n\n          // Order is important: If user provides a cssFile, those will win, because they're later\n          '{{cssStyle}}' +\n          '{{cssLink}}' +\n        '</head><body>' +\n        '<div id=\"console\">' +\n          '<div id=\"views\">' +\n            '<iframe id=\"slideView\" scrolling=\"no\"></iframe>' +\n            '<iframe id=\"preView\" scrolling=\"no\"></iframe>' +\n            '<div id=\"blocker\"></div>' +\n          '</div>' +\n          '<div id=\"notes\"></div>' +\n        '</div>' +\n        '<div id=\"controls\"> ' +\n          '<div id=\"prev\"><a  href=\"#\" onclick=\"impress().prev(); return false;\" />' +\n            '{{prev}}</a></div>' +\n          '<div id=\"next\"><a  href=\"#\" onclick=\"impress().next(); return false;\" />' +\n            '{{next}}</a></div>' +\n          '<div id=\"clock\">--:--</div>' +\n          '<div id=\"timer\" onclick=\"timerReset()\">00m 00s</div>' +\n          '<div id=\"status\">{{loading}}</div>' +\n        '</div>' +\n        '</body></html>';\n\n    // Default css location\n    var cssFileOldDefault = 'css/impressConsole.css';\n    var cssFile = undefined; // jshint ignore:line\n\n    // Css for styling iframs on the console\n    var cssFileIframeOldDefault = 'css/iframe.css';\n    var cssFileIframe = undefined; // jshint ignore:line\n\n    // All console windows, so that you can call impressConsole() repeatedly.\n    var allConsoles = {};\n\n    // Zero padding helper function:\n    var zeroPad = function( i ) {\n        return ( i < 10 ? '0' : '' ) + i;\n    };\n\n    // The console object\n    var impressConsole = window.impressConsole = function( rootId ) {\n\n        rootId = rootId || 'impress';\n\n        if ( allConsoles[ rootId ] ) {\n            return allConsoles[ rootId ];\n        }\n\n        // Root presentation elements\n        var root = document.getElementById( rootId );\n\n        var consoleWindow = null;\n\n        var nextStep = function() {\n            var classes = '';\n            var nextElement = document.querySelector( '.active' );\n\n            // Return to parents as long as there is no next sibling\n            while ( !nextElement.nextElementSibling && nextElement.parentNode ) {\n                nextElement = nextElement.parentNode;\n            }\n            nextElement = nextElement.nextElementSibling;\n            while ( nextElement ) {\n                classes = nextElement.attributes[ 'class' ];\n                if ( classes && classes.value.indexOf( 'step' ) !== -1 ) {\n                    consoleWindow.document.getElementById( 'blocker' ).innerHTML = lang.next;\n                    return nextElement;\n                }\n\n                if ( nextElement.firstElementChild ) { // First go into deep\n                    nextElement = nextElement.firstElementChild;\n                } else {\n\n                    // Go to next sibling or through parents until there is a next sibling\n                    while ( !nextElement.nextElementSibling && nextElement.parentNode ) {\n                        nextElement = nextElement.parentNode;\n                    }\n                    nextElement = nextElement.nextElementSibling;\n                }\n            }\n\n            // No next element. Pick the first\n            consoleWindow.document.getElementById( 'blocker' ).innerHTML = lang.restart;\n            return document.querySelector( '.step' );\n        };\n\n        // Sync the notes to the step\n        var onStepLeave = function() {\n            if ( consoleWindow ) {\n\n                // Set notes to next steps notes.\n                var newNotes = document.querySelector( '.active' ).querySelector( '.notes' );\n                if ( newNotes ) {\n                    newNotes = newNotes.innerHTML;\n                } else {\n                    newNotes = lang.noNotes;\n                }\n                consoleWindow.document.getElementById( 'notes' ).innerHTML = newNotes;\n\n                // Set the views\n                var baseURL = document.URL.substring( 0, document.URL.search( '#/' ) );\n                var slideSrc = baseURL + '#' + document.querySelector( '.active' ).id;\n                var preSrc = baseURL + '#' + nextStep().id;\n                var slideView = consoleWindow.document.getElementById( 'slideView' );\n\n                // Setting when already set causes glitches in Firefox, so check first:\n                if ( slideView.src !== slideSrc ) {\n                    slideView.src = slideSrc;\n                }\n                var preView = consoleWindow.document.getElementById( 'preView' );\n                if ( preView.src !== preSrc ) {\n                    preView.src = preSrc;\n                }\n\n                consoleWindow.document.getElementById( 'status' ).innerHTML =\n                    '<span class=\"moving\">' + lang.moving + '</span>';\n            }\n        };\n\n        // Sync the previews to the step\n        var onStepEnter = function() {\n            if ( consoleWindow ) {\n\n                // We do everything here again, because if you stopped the previos step to\n                // early, the onstepleave trigger is not called for that step, so\n                // we need this to sync things.\n                var newNotes = document.querySelector( '.active' ).querySelector( '.notes' );\n                if ( newNotes ) {\n                    newNotes = newNotes.innerHTML;\n                } else {\n                    newNotes = lang.noNotes;\n                }\n                var notes = consoleWindow.document.getElementById( 'notes' );\n                notes.innerHTML = newNotes;\n                notes.scrollTop = 0;\n\n                // Set the views\n                var baseURL = document.URL.substring( 0, document.URL.search( '#/' ) );\n                var slideSrc = baseURL + '#' + document.querySelector( '.active' ).id;\n                var preSrc = baseURL + '#' + nextStep().id;\n                var slideView = consoleWindow.document.getElementById( 'slideView' );\n\n                // Setting when already set causes glitches in Firefox, so check first:\n                if ( slideView.src !== slideSrc ) {\n                    slideView.src = slideSrc;\n                }\n                var preView = consoleWindow.document.getElementById( 'preView' );\n                if ( preView.src !== preSrc ) {\n                    preView.src = preSrc;\n                }\n\n                consoleWindow.document.getElementById( 'status' ).innerHTML =\n                    '<span  class=\"ready\">' + lang.ready + '</span>';\n            }\n        };\n\n        // Sync substeps\n        var onSubstep = function( event ) {\n            if ( consoleWindow ) {\n                if ( event.detail.reason === 'next' ) {\n                    onSubstepShow();\n                }\n                if ( event.detail.reason === 'prev' ) {\n                    onSubstepHide();\n                }\n            }\n        };\n\n        var onSubstepShow = function() {\n            var slideView = consoleWindow.document.getElementById( 'slideView' );\n            triggerEventInView( slideView, 'impress:substep:show' );\n        };\n\n        var onSubstepHide = function() {\n            var slideView = consoleWindow.document.getElementById( 'slideView' );\n            triggerEventInView( slideView, 'impress:substep:hide' );\n        };\n\n        var triggerEventInView = function( frame, eventName, detail ) {\n\n            // Note: Unfortunately Chrome does not allow createEvent on file:// URLs, so this won't\n            // work. This does work on Firefox, and should work if viewing the presentation on a\n            // http:// URL on Chrome.\n            var event = frame.contentDocument.createEvent( 'CustomEvent' );\n            event.initCustomEvent( eventName, true, true, detail );\n            frame.contentDocument.dispatchEvent( event );\n        };\n\n        var spaceHandler = function() {\n            var notes = consoleWindow.document.getElementById( 'notes' );\n            if ( notes.scrollTopMax - notes.scrollTop > 20 ) {\n               notes.scrollTop = notes.scrollTop + notes.clientHeight * 0.8;\n            } else {\n               window.impress().next();\n            }\n        };\n\n        var timerReset = function() {\n            consoleWindow.timerStart = new Date();\n        };\n\n        // Show a clock\n        var clockTick = function() {\n            var now = new Date();\n            var hours = now.getHours();\n            var minutes = now.getMinutes();\n            var seconds = now.getSeconds();\n            var ampm = '';\n\n            if ( lang.useAMPM ) {\n                ampm = ( hours < 12 ) ? 'AM' : 'PM';\n                hours = ( hours > 12 ) ? hours - 12 : hours;\n                hours = ( hours === 0 ) ? 12 : hours;\n            }\n\n            // Clock\n            var clockStr = zeroPad( hours ) + ':' + zeroPad( minutes ) + ':' + zeroPad( seconds ) +\n                           ' ' + ampm;\n            consoleWindow.document.getElementById( 'clock' ).firstChild.nodeValue = clockStr;\n\n            // Timer\n            seconds = Math.floor( ( now - consoleWindow.timerStart ) / 1000 );\n            minutes = Math.floor( seconds / 60 );\n            seconds = Math.floor( seconds % 60 );\n            consoleWindow.document.getElementById( 'timer' ).firstChild.nodeValue =\n                zeroPad( minutes ) + 'm ' + zeroPad( seconds ) + 's';\n\n            if ( !consoleWindow.initialized ) {\n\n                // Nudge the slide windows after load, or they will scrolled wrong on Firefox.\n                consoleWindow.document.getElementById( 'slideView' ).contentWindow.scrollTo( 0, 0 );\n                consoleWindow.document.getElementById( 'preView' ).contentWindow.scrollTo( 0, 0 );\n                consoleWindow.initialized = true;\n            }\n        };\n\n        var registerKeyEvent = function( keyCodes, handler, window ) {\n            if ( window === undefined ) {\n                window = consoleWindow;\n            }\n\n            // Prevent default keydown action when one of supported key is pressed\n            window.document.addEventListener( 'keydown', function( event ) {\n                if ( !event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey &&\n                     keyCodes.indexOf( event.keyCode ) !== -1 ) {\n                    event.preventDefault();\n                }\n            }, false );\n\n            // Trigger impress action on keyup\n            window.document.addEventListener( 'keyup', function( event ) {\n                if ( !event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey &&\n                     keyCodes.indexOf( event.keyCode ) !== -1 ) {\n                        handler();\n                        event.preventDefault();\n                }\n            }, false );\n        };\n\n        var consoleOnLoad = function() {\n                var slideView = consoleWindow.document.getElementById( 'slideView' );\n                var preView = consoleWindow.document.getElementById( 'preView' );\n\n                // Firefox:\n                slideView.contentDocument.body.classList.add( 'impress-console', 'slideView' );\n                preView.contentDocument.body.classList.add( 'impress-console', 'preView' );\n                if ( cssFileIframe !== undefined ) {\n                    slideView.contentDocument.head.insertAdjacentHTML(\n                        'beforeend',\n                        '<link rel=\"stylesheet\" type=\"text/css\" href=\"' + cssFileIframe + '\">'\n                    );\n                    preView.contentDocument.head.insertAdjacentHTML(\n                        'beforeend',\n                        '<link rel=\"stylesheet\" type=\"text/css\" href=\"' + cssFileIframe + '\">'\n                    );\n                }\n\n                // Chrome:\n                slideView.addEventListener( 'load', function() {\n                        slideView.contentDocument.body.classList.add( 'impress-console',\n                            'slideView' );\n                        if ( cssFileIframe !== undefined ) {\n                            slideView.contentDocument.head.insertAdjacentHTML(\n                                'beforeend',\n                                '<link rel=\"stylesheet\" type=\"text/css\" href=\"' +\n                                    cssFileIframe + '\">'\n                            );\n                        }\n                } );\n                preView.addEventListener( 'load', function() {\n                        preView.contentDocument.body.classList.add( 'impress-console', 'preView' );\n                        if ( cssFileIframe !== undefined ) {\n                            preView.contentDocument.head.insertAdjacentHTML(\n                                'beforeend',\n                                '<link rel=\"stylesheet\" type=\"text/css\" href=\"' +\n                                    cssFileIframe + '\">' );\n                        }\n                } );\n        };\n\n        var open = function() {\n            if ( top.isconsoleWindow ) {\n                return;\n            }\n\n            if ( consoleWindow && !consoleWindow.closed ) {\n                consoleWindow.focus();\n            } else {\n                consoleWindow = window.open( '', 'impressConsole' );\n\n                // If opening failes this may be because the browser prevents this from\n                // not (or less) interactive JavaScript...\n                if ( consoleWindow == null ) {\n\n                    // ... so I add a button to klick.\n                    // workaround on firefox\n                    var message = document.createElement( 'div' );\n                    message.id = 'impress-console-button';\n                    message.style.position = 'fixed';\n                    message.style.left = 0;\n                    message.style.top = 0;\n                    message.style.right = 0;\n                    message.style.bottom = 0;\n                    message.style.backgroundColor = 'rgba(255, 255, 255, 0.9)';\n                    var clickStr = 'var x = document.getElementById(\\'impress-console-button\\');' +\n                                     'x.parentNode.removeChild(x);' +\n                                     'var r = document.getElementById(\\'' + rootId + '\\');' +\n                                     'impress(\\'' + rootId +\n                                     '\\').lib.util.triggerEvent(r, \\'impress:console:open\\', {})';\n                    var styleStr = 'margin: 25vh 25vw;width:50vw;height:50vh;';\n                    message.innerHTML = '<button style=\"' + styleStr + '\" ' +\n                                                 'onclick=\"' + clickStr + '\">' +\n                                        lang.clickToOpen +\n                                        '</button>';\n                    document.body.appendChild( message );\n                    return;\n                }\n\n                var cssLink = '';\n                if ( cssFile !== undefined ) {\n                    cssLink = '<link rel=\"stylesheet\" type=\"text/css\" media=\"screen\" href=\"' +\n                              cssFile + '\">';\n                }\n\n                // This sets the window location to the main window location, so css can be loaded:\n                consoleWindow.document.open();\n\n                // Write the template:\n                consoleWindow.document.write(\n\n                    // CssStyleStr is lots of inline <style></style> defined at the end of this file\n                    consoleTemplate.replace( '{{cssStyle}}', cssStyleStr() )\n                                   .replace( '{{cssLink}}', cssLink )\n                                   .replace( /{{.*?}}/gi, function( x ) {\n                                       return lang[ x.substring( 2, x.length - 2 ) ]; }\n                                   )\n                );\n                consoleWindow.document.title = 'Speaker Console (' + document.title + ')';\n                consoleWindow.impress = window.impress;\n\n                // We set this flag so we can detect it later, to prevent infinite popups.\n                consoleWindow.isconsoleWindow = true;\n\n                // Set the onload function:\n                consoleWindow.onload = consoleOnLoad;\n\n                // Add clock tick\n                consoleWindow.timerStart = new Date();\n                consoleWindow.timerReset = timerReset;\n                consoleWindow.clockInterval = setInterval( allConsoles[ rootId ].clockTick, 1000 );\n\n                // Keyboard navigation handlers\n                // 33: pg up, 37: left, 38: up\n                registerKeyEvent( [ 33, 37, 38 ], window.impress().prev );\n\n                // 34: pg down, 39: right, 40: down\n                registerKeyEvent( [ 34, 39, 40 ], window.impress().next );\n\n                // 32: space\n                registerKeyEvent( [ 32 ], spaceHandler );\n\n                // 82: R\n                registerKeyEvent( [ 82 ], timerReset );\n\n                // Cleanup\n                consoleWindow.onbeforeunload = function() {\n\n                    // I don't know why onunload doesn't work here.\n                    clearInterval( consoleWindow.clockInterval );\n                };\n\n                // It will need a little nudge on Firefox, but only after loading:\n                onStepEnter();\n                consoleWindow.initialized = false;\n                consoleWindow.document.close();\n\n                //Catch any window resize to pass size on\n                window.onresize = resize;\n                consoleWindow.onresize = resize;\n\n                return consoleWindow;\n            }\n        };\n\n        var resize = function() {\n            var slideView = consoleWindow.document.getElementById( 'slideView' );\n            var preView = consoleWindow.document.getElementById( 'preView' );\n\n            // Get ratio of presentation\n            var ratio = window.innerHeight / window.innerWidth;\n\n            // Get size available for views\n            var views = consoleWindow.document.getElementById( 'views' );\n\n            // SlideView may have a border or some padding:\n            // asuming same border width on both direktions\n            var delta = slideView.offsetWidth - slideView.clientWidth;\n\n            // Set views\n            var slideViewWidth = ( views.clientWidth - delta );\n            var slideViewHeight = Math.floor( slideViewWidth * ratio );\n\n            var preViewTop = slideViewHeight + preViewGap;\n\n            var preViewWidth = Math.floor( slideViewWidth * preViewDefaultFactor );\n            var preViewHeight = Math.floor( slideViewHeight * preViewDefaultFactor );\n\n            // Shrink preview to fit into space available\n            if ( views.clientHeight - delta < preViewTop + preViewHeight ) {\n                preViewHeight = views.clientHeight - delta - preViewTop;\n                preViewWidth = Math.floor( preViewHeight / ratio );\n            }\n\n            // If preview is not high enough forget ratios!\n            if ( preViewWidth <= Math.floor( slideViewWidth * preViewMinimumFactor ) ) {\n                slideViewWidth = ( views.clientWidth - delta );\n                slideViewHeight = Math.floor( ( views.clientHeight - delta - preViewGap ) /\n                                             ( 1 + preViewMinimumFactor ) );\n\n                preViewTop = slideViewHeight + preViewGap;\n\n                preViewWidth = Math.floor( slideViewWidth * preViewMinimumFactor );\n                preViewHeight = views.clientHeight - delta - preViewTop;\n            }\n\n            // Set the calculated into styles\n            slideView.style.width = slideViewWidth + 'px';\n            slideView.style.height = slideViewHeight + 'px';\n\n            preView.style.top = preViewTop + 'px';\n\n            preView.style.width = preViewWidth + 'px';\n            preView.style.height = preViewHeight + 'px';\n        };\n\n        var _init = function( cssConsole, cssIframe ) {\n            if ( cssConsole !== undefined ) {\n                cssFile = cssConsole;\n            }\n\n            // You can also specify the css in the presentation root div:\n            // <div id=\"impress\" data-console-css=...\" data-console-css-iframe=\"...\">\n            else if ( root.dataset.consoleCss !== undefined ) {\n                cssFile = root.dataset.consoleCss;\n            }\n\n            if ( cssIframe !== undefined ) {\n                cssFileIframe = cssIframe;\n            } else if ( root.dataset.consoleCssIframe !== undefined ) {\n                cssFileIframe = root.dataset.consoleCssIframe;\n            }\n\n            // Register the event\n            root.addEventListener( 'impress:stepleave', onStepLeave );\n            root.addEventListener( 'impress:stepenter', onStepEnter );\n            root.addEventListener( 'impress:substep:stepleaveaborted', onSubstep );\n            root.addEventListener( 'impress:substep:show', onSubstepShow );\n            root.addEventListener( 'impress:substep:hide', onSubstepHide );\n\n            //When the window closes, clean up after ourselves.\n            window.onunload = function() {\n                if ( consoleWindow && !consoleWindow.closed ) {\n                    consoleWindow.close();\n                }\n            };\n\n            //Open speaker console when they press 'p'\n            registerKeyEvent( [ 80 ], open, window );\n\n            //Btw, you can also launch console automatically:\n            //<div id=\"impress\" data-console-autolaunch=\"true\">\n            if ( root.dataset.consoleAutolaunch === 'true' ) {\n                open();\n            }\n        };\n\n        var init = function( cssConsole, cssIframe ) {\n            if ( ( cssConsole === undefined || cssConsole === cssFileOldDefault ) &&\n                 ( cssIframe === undefined  || cssIframe === cssFileIframeOldDefault ) ) {\n                window.console.log( 'impressConsole().init() is deprecated. ' +\n                                   'impressConsole is now initialized automatically when you ' +\n                                   'call impress().init().' );\n            }\n            _init( cssConsole, cssIframe );\n        };\n\n        // New API for impress.js plugins is based on using events\n        root.addEventListener( 'impress:console:open', function() {\n            open();\n        } );\n\n        /**\n         * Register a key code to an event handler\n         *\n         * :param: event.detail.keyCodes    List of key codes\n         * :param: event.detail.handler     A function registered as the event handler\n         * :param: event.detail.window      The console window to register the keycode in\n         */\n        root.addEventListener( 'impress:console:registerKeyEvent', function( event ) {\n            registerKeyEvent( event.detail.keyCodes, event.detail.handler, event.detail.window );\n        } );\n\n        // Return the object\n        allConsoles[ rootId ] = { init: init, open: open, clockTick: clockTick,\n                               registerKeyEvent: registerKeyEvent, _init: _init };\n        return allConsoles[ rootId ];\n\n    };\n\n    // This initializes impressConsole automatically when initializing impress itself\n    document.addEventListener( 'impress:init', function( event ) {\n\n        // Note: impressConsole wants the id string, not the DOM element directly\n        impressConsole( event.target.id )._init();\n\n        // Add 'P' to the help popup\n        triggerEvent( document, 'impress:help:add',\n                        { command: 'P', text: 'Presenter console', row: 10 } );\n    } );\n\n    // Returns a string to be used inline as a css <style> element in the console window.\n    // Apologies for length, but hiding it here at the end to keep it away from rest of the code.\n    var cssStyleStr = function() {\n        return `<style>\n            #impressconsole body {\n                background-color: rgb(255, 255, 255);\n                padding: 0;\n                margin: 0;\n                font-family: verdana, arial, sans-serif;\n                font-size: 2vw;\n            }\n\n            #impressconsole div#console {\n                position: absolute;\n                top: 0.5vw;\n                left: 0.5vw;\n                right: 0.5vw;\n                bottom: 3vw;\n                margin: 0;\n            }\n\n            #impressconsole div#views, #impressconsole div#notes {\n                position: absolute;\n                top: 0;\n                bottom: 0;\n            }\n\n            #impressconsole div#views {\n                left: 0;\n                right: 50vw;\n                overflow: hidden;\n            }\n\n            #impressconsole div#blocker {\n                position: absolute;\n                right: 0;\n                bottom: 0;\n            }\n\n            #impressconsole div#notes {\n                left: 50vw;\n                right: 0;\n                overflow-x: hidden;\n                overflow-y: auto;\n                padding: 0.3ex;\n                background-color: rgb(255, 255, 255);\n                border: solid 1px rgb(120, 120, 120);\n            }\n\n            #impressconsole div#notes .noNotes {\n                color: rgb(200, 200, 200);\n            }\n\n            #impressconsole div#notes p {\n                margin-top: 0;\n            }\n\n            #impressconsole iframe {\n                position: absolute;\n                margin: 0;\n                padding: 0;\n                left: 0;\n                border: solid 1px rgb(120, 120, 120);\n            }\n\n            #impressconsole iframe#slideView {\n                top: 0;\n                width: 49vw;\n                height: 49vh;\n            }\n\n            #impressconsole iframe#preView {\n                opacity: 0.7;\n                top: 50vh;\n                width: 30vw;\n                height: 30vh;\n            }\n\n            #impressconsole div#controls {\n                margin: 0;\n                position: absolute;\n                bottom: 0.25vw;\n                left: 0.5vw;\n                right: 0.5vw;\n                height: 2.5vw;\n                background-color: rgb(255, 255, 255);\n                background-color: rgba(255, 255, 255, 0.6);\n            }\n\n            #impressconsole div#prev, div#next {\n            }\n\n            #impressconsole div#prev a, #impressconsole div#next a {\n                display: block;\n                border: solid 1px rgb(70, 70, 70);\n                border-radius: 0.5vw;\n                font-size: 1.5vw;\n                padding: 0.25vw;\n                text-decoration: none;\n                background-color: rgb(220, 220, 220);\n                color: rgb(0, 0, 0);\n            }\n\n            #impressconsole div#prev a:hover, #impressconsole div#next a:hover {\n                background-color: rgb(245, 245, 245);\n            }\n\n            #impressconsole div#prev {\n                float: left;\n            }\n\n            #impressconsole div#next {\n                float: right;\n            }\n\n            #impressconsole div#status {\n                margin-left: 2em;\n                margin-right: 2em;\n                text-align: center;\n                float: right;\n            }\n\n            #impressconsole div#clock {\n                margin-left: 2em;\n                margin-right: 2em;\n                text-align: center;\n                float: left;\n            }\n\n            #impressconsole div#timer {\n                margin-left: 2em;\n                margin-right: 2em;\n                text-align: center;\n                float: left;\n            }\n\n            #impressconsole span.moving {\n                color: rgb(255, 0, 0);\n            }\n\n            #impressconsole span.ready {\n                color: rgb(0, 128, 0);\n            }\n        </style>`;\n    };\n\n} )( document, window );\n\n/**\n * Media Plugin\n *\n * This plugin will do the following things:\n *\n *  - Add a special class when playing (body.impress-media-video-playing\n *    and body.impress-media-video-playing) and pausing media (body.impress-media-video-paused\n *    and body.impress-media-audio-paused) (removing them when ending).\n *    This can be useful for example for darkening the background or fading out other elements\n *    while a video is playing.\n *    Only media at the current step are taken into account. All classes are removed when leaving\n *    a step.\n *\n *  - Introduce the following new data attributes:\n *\n *    - data-media-autoplay=\"true\": Autostart media when entering its step.\n *    - data-media-autostop=\"true\": Stop media (= pause and reset to start), when leaving its\n *      step.\n *    - data-media-autopause=\"true\": Pause media but keep current time when leaving its step.\n *\n *    When these attributes are added to a step they are inherited by all media on this step.\n *    Of course this setting can be overwritten by adding different attributes to inidvidual\n *    media.\n *\n *    The same rule applies when this attributes is added to the root element. Settings can be\n *    overwritten for individual steps and media.\n *\n *    Examples:\n *    - data-media-autoplay=\"true\" data-media-autostop=\"true\": start media on enter, stop on\n *      leave, restart from beginning when re-entering the step.\n *\n *    - data-media-autoplay=\"true\" data-media-autopause=\"true\": start media on enter, pause on\n *      leave, resume on re-enter\n *\n *    - data-media-autoplay=\"true\" data-media-autostop=\"true\" data-media-autopause=\"true\": start\n *      media on enter, stop on leave (stop overwrites pause).\n *\n *    - data-media-autoplay=\"true\" data-media-autopause=\"false\": let media start automatically\n *      when entering a step and let it play when leaving the step.\n *\n *    - <div id=\"impress\" data-media-autoplay=\"true\"> ... <div class=\"step\"\n *      data-media-autoplay=\"false\">\n *      All media is startet automatically on all steps except the one that has the\n *      data-media-autoplay=\"false\" attribute.\n *\n *  - Pro tip: Use <audio onended=\"impress().next()\"> or <video onended=\"impress().next()\"> to\n *    proceed to the next step automatically, when the end of the media is reached.\n *\n *\n * Copyright 2018 Holger Teichert (@complanar)\n * Released under the MIT license.\n */\n/* global window, document */\n\n( function( document, window ) {\n    \"use strict\";\n    var root, api, gc, attributeTracker;\n\n    attributeTracker = [];\n\n    // Function names\n    var enhanceMediaNodes,\n        enhanceMedia,\n        removeMediaClasses,\n        onStepenterDetectImpressConsole,\n        onStepenter,\n        onStepleave,\n        onPlay,\n        onPause,\n        onEnded,\n        getMediaAttribute,\n        teardown;\n\n    document.addEventListener( \"impress:init\", function( event ) {\n        root = event.target;\n        api = event.detail.api;\n        gc = api.lib.gc;\n\n        enhanceMedia();\n\n        gc.pushCallback( teardown );\n    }, false );\n\n    teardown = function() {\n        var el, i;\n        removeMediaClasses();\n        for ( i = 0; i < attributeTracker.length; i += 1 ) {\n            el = attributeTracker[ i ];\n            el.node.removeAttribute( el.attr );\n        }\n        attributeTracker = [];\n    };\n\n    getMediaAttribute = function( attributeName, nodes ) {\n        var attrName, attrValue, i, node;\n        attrName = \"data-media-\" + attributeName;\n\n        // Look for attributes in all nodes\n        for ( i = 0; i < nodes.length; i += 1 ) {\n            node = nodes[ i ];\n\n            // First test, if the attribute exists, because some browsers may return\n            // an empty string for non-existing attributes - specs are not clear at that point\n            if ( node.hasAttribute( attrName ) ) {\n\n                // Attribute found, return their parsed boolean value, empty strings count as true\n                // to enable empty value booleans (common in html5 but not allowed in well formed\n                // xml).\n                attrValue = node.getAttribute( attrName );\n                if ( attrValue === \"\" || attrValue === \"true\" ) {\n                    return true;\n                } else {\n                    return false;\n                }\n            }\n\n            // No attribute found at current node, proceed with next round\n        }\n\n        // Last resort: no attribute found - return undefined to distiguish from false\n        return undefined;\n    };\n\n    onPlay = function( event ) {\n        var type = event.target.nodeName.toLowerCase();\n        document.body.classList.add( \"impress-media-\" + type + \"-playing\" );\n        document.body.classList.remove( \"impress-media-\" + type + \"-paused\" );\n    };\n\n    onPause = function( event ) {\n        var type = event.target.nodeName.toLowerCase();\n        document.body.classList.add( \"impress-media-\" + type + \"-paused\" );\n        document.body.classList.remove( \"impress-media-\" + type + \"-playing\" );\n    };\n\n    onEnded = function( event ) {\n        var type = event.target.nodeName.toLowerCase();\n        document.body.classList.remove( \"impress-media-\" + type + \"-playing\" );\n        document.body.classList.remove( \"impress-media-\" + type + \"-paused\" );\n    };\n\n    removeMediaClasses = function() {\n        var type, types;\n        types = [ \"video\", \"audio\" ];\n        for ( type in types ) {\n            document.body.classList.remove( \"impress-media-\" + types[ type ] + \"-playing\" );\n            document.body.classList.remove( \"impress-media-\" + types[ type ] + \"-paused\" );\n        }\n    };\n\n    enhanceMediaNodes = function() {\n        var i, id, media, mediaElement, type;\n\n        media = root.querySelectorAll( \"audio, video\" );\n        for ( i = 0; i < media.length; i += 1 ) {\n            type = media[ i ].nodeName.toLowerCase();\n\n            // Set an id to identify each media node - used e.g. for cross references by\n            // the consoleMedia plugin\n            mediaElement = media[ i ];\n            id = mediaElement.getAttribute( \"id\" );\n            if ( id === undefined || id === null ) {\n                mediaElement.setAttribute( \"id\", \"media-\" + type + \"-\" + i );\n                attributeTracker.push( { \"node\": mediaElement, \"attr\": \"id\" } );\n            }\n            gc.addEventListener( mediaElement, \"play\", onPlay );\n            gc.addEventListener( mediaElement, \"playing\", onPlay );\n            gc.addEventListener( mediaElement, \"pause\", onPause );\n            gc.addEventListener( mediaElement, \"ended\", onEnded );\n        }\n    };\n\n    enhanceMedia = function() {\n        var steps, stepElement, i;\n        enhanceMediaNodes();\n        steps = document.getElementsByClassName( \"step\" );\n        for ( i = 0; i < steps.length; i += 1 ) {\n            stepElement = steps[ i ];\n\n            gc.addEventListener( stepElement, \"impress:stepenter\", onStepenter );\n            gc.addEventListener( stepElement, \"impress:stepleave\", onStepleave );\n        }\n    };\n\n    onStepenterDetectImpressConsole = function() {\n        return {\n            \"preview\": ( window.frameElement !== null && window.frameElement.id === \"preView\" ),\n            \"slideView\": ( window.frameElement !== null && window.frameElement.id === \"slideView\" )\n        };\n    };\n\n    onStepenter = function( event ) {\n        var stepElement, media, mediaElement, i, onConsole, autoplay;\n        if ( ( !event ) || ( !event.target ) ) {\n            return;\n        }\n\n        stepElement = event.target;\n        removeMediaClasses();\n\n        media = stepElement.querySelectorAll( \"audio, video\" );\n        for ( i = 0; i < media.length; i += 1 ) {\n            mediaElement = media[ i ];\n\n            // Autoplay when (maybe inherited) autoplay setting is true,\n            // but only if not on preview of the next step in impressConsole\n            onConsole = onStepenterDetectImpressConsole();\n            autoplay = getMediaAttribute( \"autoplay\", [ mediaElement, stepElement, root ] );\n            if ( autoplay && !onConsole.preview ) {\n                if ( onConsole.slideView ) {\n                    mediaElement.muted = true;\n                }\n                mediaElement.play();\n            }\n        }\n    };\n\n    onStepleave = function( event ) {\n        var stepElement, media, i, mediaElement, autoplay, autopause, autostop;\n        if ( ( !event || !event.target ) ) {\n            return;\n        }\n\n        stepElement = event.target;\n        media = event.target.querySelectorAll( \"audio, video\" );\n        for ( i = 0; i < media.length; i += 1 ) {\n            mediaElement = media[ i ];\n\n            autoplay = getMediaAttribute( \"autoplay\", [ mediaElement, stepElement, root ] );\n            autopause = getMediaAttribute( \"autopause\", [ mediaElement, stepElement, root ] );\n            autostop = getMediaAttribute( \"autostop\",  [ mediaElement, stepElement, root ] );\n\n            // If both autostop and autopause are undefined, set it to the value of autoplay\n            if ( autostop === undefined && autopause === undefined ) {\n                autostop = autoplay;\n            }\n\n            if ( autopause || autostop ) {\n                mediaElement.pause();\n                if ( autostop ) {\n                    mediaElement.currentTime = 0;\n                }\n            }\n        }\n        removeMediaClasses();\n    };\n\n} )( document, window );\n\n/**\n * Mobile devices support\n *\n * Allow presentation creators to hide all but 3 slides, to save resources, particularly on mobile\n * devices, using classes body.impress-mobile, .step.prev, .step.active and .step.next.\n *\n * Note: This plugin does not take into account possible redirections done with skip, goto etc\n * plugins. Basically it wouldn't work as intended in such cases, but the active step will at least\n * be correct.\n *\n * Adapted to a plugin from a submission by @Kzeni:\n * https://github.com/impress/impress.js/issues/333\n */\n/* global document, navigator */\n( function( document ) {\n    \"use strict\";\n\n    var getNextStep = function( el ) {\n        var steps = document.querySelectorAll( \".step\" );\n        for ( var i = 0; i < steps.length; i++ ) {\n            if ( steps[ i ] === el ) {\n                if ( i + 1 < steps.length ) {\n                    return steps[ i + 1 ];\n                } else {\n                    return steps[ 0 ];\n                }\n            }\n        }\n    };\n    var getPrevStep = function( el ) {\n        var steps = document.querySelectorAll( \".step\" );\n        for ( var i = steps.length - 1; i >= 0; i-- ) {\n            if ( steps[ i ] === el ) {\n                if ( i - 1 >= 0 ) {\n                    return steps[ i - 1 ];\n                } else {\n                    return steps[ steps.length - 1 ];\n                }\n            }\n        }\n    };\n\n    // Detect mobile browsers & add CSS class as appropriate.\n    document.addEventListener( \"impress:init\", function( event ) {\n        var body = document.body;\n        if ( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(\n                 navigator.userAgent\n             ) ) {\n            body.classList.add( \"impress-mobile\" );\n        }\n\n        // Unset all this on teardown\n        var api = event.detail.api;\n        api.lib.gc.pushCallback( function() {\n            document.body.classList.remove( \"impress-mobile\" );\n            var prev = document.getElementsByClassName( \"prev\" )[ 0 ];\n            var next = document.getElementsByClassName( \"next\" )[ 0 ];\n            if ( typeof prev !== \"undefined\" ) {\n                prev.classList.remove( \"prev\" );\n            }\n            if ( typeof next !== \"undefined\" ) {\n                next.classList.remove( \"next\" );\n            }\n        } );\n    } );\n\n    // Add prev and next classes to the siblings of the newly entered active step element\n    // Remove prev and next classes from their current step elements\n    // Note: As an exception we break namespacing rules, as these are useful general purpose\n    // classes. (Naming rules would require us to use css classes mobile-next and mobile-prev,\n    // based on plugin name.)\n    document.addEventListener( \"impress:stepenter\", function( event ) {\n\t      var oldprev = document.getElementsByClassName( \"prev\" )[ 0 ];\n\t      var oldnext = document.getElementsByClassName( \"next\" )[ 0 ];\n\n\t      var prev = getPrevStep( event.target );\n\t      prev.classList.add( \"prev\" );\n\t      var next = getNextStep( event.target );\n\t      next.classList.add( \"next\" );\n\n\t      if ( typeof oldprev !== \"undefined\" ) {\n\t\t      oldprev.classList.remove( \"prev\" );\n              }\n\t      if ( typeof oldnext !== \"undefined\" ) {\n\t\t      oldnext.classList.remove( \"next\" );\n              }\n    } );\n} )( document );\n\n\n/**\n * Mouse timeout plugin\n *\n * After 3 seconds of mouse inactivity, add the css class\n * `body.impress-mouse-timeout`. On `mousemove`, `click` or `touch`, remove the\n * class.\n *\n * The use case for this plugin is to use CSS to hide elements from the screen\n * and only make them visible when the mouse is moved. Examples where this\n * might be used are: the toolbar from the toolbar plugin, and the mouse cursor\n * itself.\n *\n * Example CSS:\n *\n *     body.impress-mouse-timeout {\n *         cursor: none;\n *     }\n *     body.impress-mouse-timeout div#impress-toolbar {\n *         display: none;\n *     }\n *\n *\n * Copyright 2016 Henrik Ingo (@henrikingo)\n * Released under the MIT license.\n */\n/* global window, document */\n( function( document, window ) {\n    \"use strict\";\n    var timeout = 3;\n    var timeoutHandle;\n\n    var hide = function() {\n\n        // Mouse is now inactive\n        document.body.classList.add( \"impress-mouse-timeout\" );\n    };\n\n    var show = function() {\n        if ( timeoutHandle ) {\n            window.clearTimeout( timeoutHandle );\n        }\n\n        // Mouse is now active\n        document.body.classList.remove( \"impress-mouse-timeout\" );\n\n        // Then set new timeout after which it is considered inactive again\n        timeoutHandle = window.setTimeout( hide, timeout * 1000 );\n    };\n\n    document.addEventListener( \"impress:init\", function( event ) {\n        var api = event.detail.api;\n        var gc = api.lib.gc;\n        gc.addEventListener( document, \"mousemove\", show );\n        gc.addEventListener( document, \"click\", show );\n        gc.addEventListener( document, \"touch\", show );\n\n        // Set first timeout\n        show();\n\n        // Unset all this on teardown\n        gc.pushCallback( function() {\n            window.clearTimeout( timeoutHandle );\n            document.body.classList.remove( \"impress-mouse-timeout\" );\n        } );\n    }, false );\n\n} )( document, window );\n\n/**\n * Navigation events plugin\n *\n * As you can see this part is separate from the impress.js core code.\n * It's because these navigation actions only need what impress.js provides with\n * its simple API.\n *\n * This plugin is what we call an _init plugin_. It's a simple kind of\n * impress.js plugin. When loaded, it starts listening to the `impress:init`\n * event. That event listener initializes the plugin functionality - in this\n * case we listen to some keypress and mouse events. The only dependencies on\n * core impress.js functionality is the `impress:init` method, as well as using\n * the public api `next(), prev(),` etc when keys are pressed.\n *\n * Copyright 2011-2012 Bartek Szopka (@bartaz)\n * Released under the MIT license.\n * ------------------------------------------------\n *  author:  Bartek Szopka\n *  version: 0.5.3\n *  url:     http://bartaz.github.com/impress.js/\n *  source:  http://github.com/bartaz/impress.js/\n *\n */\n/* global document */\n( function( document ) {\n    \"use strict\";\n\n    // Wait for impress.js to be initialized\n    document.addEventListener( \"impress:init\", function( event ) {\n\n        // Getting API from event data.\n        // So you don't event need to know what is the id of the root element\n        // or anything. `impress:init` event data gives you everything you\n        // need to control the presentation that was just initialized.\n        var api = event.detail.api;\n        var gc = api.lib.gc;\n        var util = api.lib.util;\n\n        // Supported keys are:\n        // [space] - quite common in presentation software to move forward\n        // [up] [right] / [down] [left] - again common and natural addition,\n        // [pgdown] / [pgup] - often triggered by remote controllers,\n        // [tab] - this one is quite controversial, but the reason it ended up on\n        //   this list is quite an interesting story... Remember that strange part\n        //   in the impress.js code where window is scrolled to 0,0 on every presentation\n        //   step, because sometimes browser scrolls viewport because of the focused element?\n        //   Well, the [tab] key by default navigates around focusable elements, so clicking\n        //   it very often caused scrolling to focused element and breaking impress.js\n        //   positioning. I didn't want to just prevent this default action, so I used [tab]\n        //   as another way to moving to next step... And yes, I know that for the sake of\n        //   consistency I should add [shift+tab] as opposite action...\n        var isNavigationEvent = function( event ) {\n\n            // Don't trigger navigation for example when user returns to browser window with ALT+TAB\n            if ( event.altKey || event.ctrlKey || event.metaKey ) {\n                return false;\n            }\n\n            // In the case of TAB, we force step navigation always, overriding the browser\n            // navigation between input elements, buttons and links.\n            if ( event.keyCode === 9 ) {\n                return true;\n            }\n\n            // With the sole exception of TAB, we also ignore keys pressed if shift is down.\n            if ( event.shiftKey ) {\n                return false;\n            }\n\n            if ( ( event.keyCode >= 32 && event.keyCode <= 34 ) ||\n                 ( event.keyCode >= 37 && event.keyCode <= 40 ) ) {\n                return true;\n            }\n        };\n\n        // KEYBOARD NAVIGATION HANDLERS\n\n        // Prevent default keydown action when one of supported key is pressed.\n        gc.addEventListener( document, \"keydown\", function( event ) {\n            if ( isNavigationEvent( event ) ) {\n                event.preventDefault();\n            }\n        }, false );\n\n        // Trigger impress action (next or prev) on keyup.\n        gc.addEventListener( document, \"keyup\", function( event ) {\n            if ( isNavigationEvent( event ) ) {\n                if ( event.shiftKey ) {\n                    switch ( event.keyCode ) {\n                        case 9: // Shift+tab\n                            api.prev();\n                            break;\n                    }\n                } else {\n                    switch ( event.keyCode ) {\n                        case 33: // Pg up\n                        case 37: // Left\n                        case 38: // Up\n                                 api.prev( event );\n                                 break;\n                        case 9:  // Tab\n                        case 32: // Space\n                        case 34: // Pg down\n                        case 39: // Right\n                        case 40: // Down\n                                 api.next( event );\n                                 break;\n                    }\n                }\n                event.preventDefault();\n            }\n        }, false );\n\n        // Delegated handler for clicking on the links to presentation steps\n        gc.addEventListener( document, \"click\", function( event ) {\n\n            // Event delegation with \"bubbling\"\n            // check if event target (or any of its parents is a link)\n            var target = event.target;\n            try {\n                while ( ( target.tagName !== \"A\" ) &&\n                        ( target !== document.documentElement ) ) {\n                    target = target.parentNode;\n                }\n\n                if ( target.tagName === \"A\" ) {\n                    var href = target.getAttribute( \"href\" );\n\n                    // If it's a link to presentation step, target this step\n                    if ( href && href[ 0 ] === \"#\" ) {\n                        target = document.getElementById( href.slice( 1 ) );\n                    }\n                }\n\n                if ( api.goto( target ) ) {\n                    event.stopImmediatePropagation();\n                    event.preventDefault();\n                }\n            }\n            catch ( err ) {\n\n                // For example, when clicking on the button to launch speaker console, the button\n                // is immediately deleted from the DOM. In this case target is a DOM element when\n                // we get it, but turns out to be null if you try to actually do anything with it.\n                if ( err instanceof TypeError &&\n                     err.message === \"target is null\" ) {\n                    return;\n                }\n                throw err;\n            }\n        }, false );\n\n        // Delegated handler for clicking on step elements\n        gc.addEventListener( document, \"click\", function( event ) {\n            var target = event.target;\n            try {\n\n                // Find closest step element that is not active\n                while ( !( target.classList.contains( \"step\" ) &&\n                        !target.classList.contains( \"active\" ) ) &&\n                        ( target !== document.documentElement ) ) {\n                    target = target.parentNode;\n                }\n\n                if ( api.goto( target ) ) {\n                    event.preventDefault();\n                }\n            }\n            catch ( err ) {\n\n                // For example, when clicking on the button to launch speaker console, the button\n                // is immediately deleted from the DOM. In this case target is a DOM element when\n                // we get it, but turns out to be null if you try to actually do anything with it.\n                if ( err instanceof TypeError &&\n                     err.message === \"target is null\" ) {\n                    return;\n                }\n                throw err;\n            }\n        }, false );\n\n        // Add a line to the help popup\n        util.triggerEvent( document, \"impress:help:add\", { command: \"Left &amp; Right\",\n                                                           text: \"Previous &amp; Next step\",\n                                                           row: 1 } );\n\n    }, false );\n\n} )( document );\n\n\n/**\n * Navigation UI plugin\n *\n * This plugin provides UI elements \"back\", \"forward\" and a list to select\n * a specific slide number.\n *\n * The navigation controls are added to the toolbar plugin via DOM events. User must enable the\n * toolbar in a presentation to have them visible.\n *\n * Copyright 2016 Henrik Ingo (@henrikingo)\n * Released under the MIT license.\n */\n\n// This file contains so much HTML, that we will just respectfully disagree about js\n/* jshint quotmark:single */\n/* global document */\n\n( function( document ) {\n    'use strict';\n    var toolbar;\n    var api;\n    var root;\n    var steps;\n    var hideSteps = [];\n    var prev;\n    var select;\n    var next;\n\n    var triggerEvent = function( el, eventName, detail ) {\n        var event = document.createEvent( 'CustomEvent' );\n        event.initCustomEvent( eventName, true, true, detail );\n        el.dispatchEvent( event );\n    };\n\n    var makeDomElement = function( html ) {\n        var tempDiv = document.createElement( 'div' );\n        tempDiv.innerHTML = html;\n        return tempDiv.firstChild;\n    };\n\n    var getStepTitle = function( step ) {\n\n        // Max length for title.\n        // Line longer than this will be cutted.\n        const MAX_TITLE_LEN = 40;\n\n        if ( step.title ) {\n            return step.title;\n        }\n\n        // Neither title nor id is defined\n        if ( step.id.startsWith( 'step-' ) ) {\n            for ( var line of step.innerText.split( '\\n' ) ) {\n                line = line.trim( );\n                if ( line.length > 0 ) {\n                    if ( line.length <= MAX_TITLE_LEN ) {\n                        return line;\n                    } else {\n                        return line.slice( 0, MAX_TITLE_LEN - 3 ) + '...';\n                    }\n                }\n            }\n        }\n\n        return step.id;\n    };\n\n    var selectOptionsHtml = function() {\n        var options = '';\n        for ( var i = 0; i < steps.length; i++ ) {\n\n            // Omit steps that are listed as hidden from select widget\n            if ( hideSteps.indexOf( steps[ i ] ) < 0 ) {\n                options = options + '<option value=\"' + steps[ i ].id + '\">' + // jshint ignore:line\n\t\t\t\t\t\t\t(\n\t\t\t\t\t\t\t\tgetStepTitle( steps[ i ] )\n\t\t\t\t\t\t\t) + '</option>' + '\\n';\n            }\n        }\n        return options;\n    };\n\n    var addNavigationControls = function( event ) {\n        api = event.detail.api;\n        var gc = api.lib.gc;\n        root = event.target;\n        steps = root.querySelectorAll( '.step' );\n\n        var prevHtml   = '<button id=\"impress-navigation-ui-prev\" title=\"Previous\" ' +\n                         'class=\"impress-navigation-ui\">&lt;</button>';\n        var selectHtml = '<select id=\"impress-navigation-ui-select\" title=\"Go to\" ' +\n                         'class=\"impress-navigation-ui\">' + '\\n' +\n                           selectOptionsHtml() +\n                           '</select>';\n        var nextHtml   = '<button id=\"impress-navigation-ui-next\" title=\"Next\" ' +\n                         'class=\"impress-navigation-ui\">&gt;</button>';\n\n        prev = makeDomElement( prevHtml );\n        prev.addEventListener( 'click',\n            function() {\n                api.prev();\n        } );\n        select = makeDomElement( selectHtml );\n        select.addEventListener( 'change',\n            function( event ) {\n                api.goto( event.target.value );\n        } );\n        gc.addEventListener( root, 'impress:steprefresh', function( event ) {\n\n            // As impress.js core now allows to dynamically edit the steps, including adding,\n            // removing, and reordering steps, we need to requery and redraw the select list on\n            // every stepenter event.\n            steps = root.querySelectorAll( '.step' );\n            select.innerHTML = '\\n' + selectOptionsHtml();\n\n            // Make sure the list always shows the step we're actually on, even if it wasn't\n            // selected from the list\n            select.value = event.target.id;\n        } );\n        next = makeDomElement( nextHtml );\n        next.addEventListener( 'click',\n            function() {\n                api.next();\n        } );\n\n        triggerEvent( toolbar, 'impress:toolbar:appendChild', { group: 0, element: prev } );\n        triggerEvent( toolbar, 'impress:toolbar:appendChild', { group: 0, element: select } );\n        triggerEvent( toolbar, 'impress:toolbar:appendChild', { group: 0, element: next } );\n\n    };\n\n    // API for not listing given step in the select widget.\n    // For example, if you set class=\"skip\" on some element, you may not want it to show up in the\n    // list either. Otoh we cannot assume that, or anything else, so steps that user wants omitted\n    // must be specifically added with this API call.\n    document.addEventListener( 'impress:navigation-ui:hideStep', function( event ) {\n        hideSteps.push( event.target );\n        if ( select ) {\n            select.innerHTML = selectOptionsHtml();\n        }\n    }, false );\n\n    // Wait for impress.js to be initialized\n    document.addEventListener( 'impress:init', function( event ) {\n        toolbar = document.querySelector( '#impress-toolbar' );\n        if ( toolbar ) {\n            addNavigationControls( event );\n        }\n    }, false );\n\n} )( document );\n\n\n/* global document */\n( function( document ) {\n    \"use strict\";\n    var root;\n    var stepids = [];\n\n    // Get stepids from the steps under impress root\n    var getSteps = function() {\n        stepids = [];\n        var steps = root.querySelectorAll( \".step\" );\n        for ( var i = 0; i < steps.length; i++ )\n        {\n          stepids[ i + 1 ] = steps[ i ].id;\n        }\n        };\n\n    // Wait for impress.js to be initialized\n    document.addEventListener( \"impress:init\", function( event ) {\n            root = event.target;\n        getSteps();\n        var gc = event.detail.api.lib.gc;\n        gc.pushCallback( function() {\n            stepids = [];\n            if ( progressbar ) {\n                progressbar.style.width = \"\";\n                        }\n            if ( progress ) {\n                progress.innerHTML = \"\";\n                        }\n        } );\n    } );\n\n    var progressbar = document.querySelector( \"div.impress-progressbar div\" );\n    var progress = document.querySelector( \"div.impress-progress\" );\n\n    if ( null !== progressbar || null !== progress ) {\n        document.addEventListener( \"impress:stepleave\", function( event ) {\n            updateProgressbar( event.detail.next.id );\n        } );\n\n        document.addEventListener( \"impress:steprefresh\", function( event ) {\n            getSteps();\n            updateProgressbar( event.target.id );\n        } );\n\n    }\n\n    function updateProgressbar( slideId ) {\n        var slideNumber = stepids.indexOf( slideId );\n        if ( null !== progressbar ) {\n                        var width = 100 / ( stepids.length - 1 ) * ( slideNumber );\n            progressbar.style.width = width.toFixed( 2 ) + \"%\";\n        }\n        if ( null !== progress ) {\n            progress.innerHTML = slideNumber + \"/\" + ( stepids.length - 1 );\n        }\n    }\n} )( document );\n\n/**\n * Relative Positioning Plugin\n *\n * This plugin provides support for defining the coordinates of a step relative\n * to the previous step. This is often more convenient when creating presentations,\n * since as you add, remove or move steps, you may not need to edit the positions\n * as much as is the case with the absolute coordinates supported by impress.js\n * core.\n *\n * Example:\n *\n *         <!-- Position step 1000 px to the right and 500 px up from the previous step. -->\n *         <div class=\"step\" data-rel-x=\"1000\" data-rel-y=\"500\">\n *\n * Following html attributes are supported for step elements:\n *\n *     data-rel-x\n *     data-rel-y\n *     data-rel-z\n *\n * These values are also inherited from the previous step. This makes it easy to\n * create a boring presentation where each slide shifts for example 1000px down\n * from the previous.\n *\n * In addition to plain numbers, which are pixel values, it is also possible to\n * define relative positions as a multiple of screen height and width, using\n * a unit of \"h\" and \"w\", respectively, appended to the number.\n *\n * Example:\n *\n *        <div class=\"step\" data-rel-x=\"1.5w\" data-rel-y=\"1.5h\">\n *\n * This plugin is a *pre-init plugin*. It is called synchronously from impress.js\n * core at the beginning of `impress().init()`. This allows it to process its own\n * data attributes first, and possibly alter the data-x, data-y and data-z attributes\n * that will then be processed by `impress().init()`.\n *\n * (Another name for this kind of plugin might be called a *filter plugin*, but\n * *pre-init plugin* is more generic, as a plugin might do whatever it wants in\n * the pre-init stage.)\n *\n * Copyright 2016 Henrik Ingo (@henrikingo)\n * Released under the MIT license.\n */\n\n/* global document, window */\n\n( function( document, window ) {\n    \"use strict\";\n\n    var api;\n    var startingState = {};\n\n    var toNumber;\n    var toNumberAdvanced;\n\n    var computeRelativePositions = function( el, prev ) {\n        var data = el.dataset;\n\n        if ( !prev ) {\n\n            // For the first step, inherit these defaults\n            prev = {\n                x:0, y:0, z:0,\n                rotate: { x:0, y:0, z:0, order:\"xyz\" },\n                relative: {\n                    position: \"absolute\",\n                    x:0, y:0, z:0,\n                    rotate: { x:0, y:0, z:0, order:\"xyz\" }\n                }\n            };\n        }\n\n        var ref = prev;\n        if ( data.relTo ) {\n\n            ref = document.getElementById( data.relTo );\n            if ( ref ) {\n\n                // Test, if it is a previous step that already has some assigned position data\n                if ( el.compareDocumentPosition( ref ) & Node.DOCUMENT_POSITION_PRECEDING ) {\n                    prev.x = toNumberAdvanced( ref.getAttribute( \"data-x\" ) );\n                    prev.y = toNumberAdvanced( ref.getAttribute( \"data-y\" ) );\n                    prev.z = toNumberAdvanced( ref.getAttribute( \"data-z\" ) );\n\n                    var prevPosition = ref.getAttribute( \"data-rel-position\" ) || \"absolute\";\n\n                    if ( prevPosition !== \"relative\" ) {\n\n                        // For compatibility with the old behavior, doesn't inherit otherthings,\n                        // just like a reset.\n                        prev.rotate = { x:0, y:0, z:0, order: \"xyz\" };\n                        prev.relative = {\n                            position: \"absolute\",\n                            x:0, y:0, z:0,\n                            rotate: { x:0, y:0, z:0, order:\"xyz\" }\n                        };\n                    } else {\n\n                        // For data-rel-position=\"relative\", inherit all\n                        prev.rotate.y = toNumber( ref.getAttribute( \"data-rotate-y\" ) );\n                        prev.rotate.x = toNumber( ref.getAttribute( \"data-rotate-x\" ) );\n                        prev.rotate.z = toNumber(\n                            ref.getAttribute( \"data-rotate-z\" ) ||\n                            ref.getAttribute( \"data-rotate\" ) );\n\n                        // We also inherit relatives from relTo slide\n                        prev.relative = {\n                            position: prevPosition,\n                            x: toNumberAdvanced( ref.getAttribute( \"data-rel-x\" ), 0 ),\n                            y: toNumberAdvanced( ref.getAttribute( \"data-rel-y\" ), 0 ),\n                            z: toNumberAdvanced( ref.getAttribute( \"data-rel-z\" ), 0 ),\n                            rotate: {\n                                x: toNumberAdvanced( ref.getAttribute( \"data-rel-rotate-x\" ), 0 ),\n                                y: toNumberAdvanced( ref.getAttribute( \"data-rel-rotate-y\" ), 0 ),\n                                z: toNumberAdvanced( ref.getAttribute( \"data-rel-rotate-z\" ), 0 ),\n                                order: ( ref.getAttribute( \"data-rel-rotate-order\" ) ||  \"xyz\" )\n                            }\n                        };\n                    }\n                } else {\n                    window.console.error(\n                        \"impress.js rel plugin: Step \\\"\" + data.relTo + \"\\\" is not defined \" +\n                        \"*before* the current step. Referencing is limited to previously defined \" +\n                        \"steps. Please check your markup. Ignoring data-rel-to attribute of \" +\n                        \"this step. Have a look at the documentation for how to create relative \" +\n                        \"positioning to later shown steps with the help of the goto plugin.\"\n                    );\n                }\n            } else {\n\n                // Step not found\n                window.console.warn(\n                    \"impress.js rel plugin: \\\"\" + data.relTo + \"\\\" is not a valid step in this \" +\n                    \"impress.js presentation. Please check your markup. Ignoring data-rel-to \" +\n                    \"attribute of this step.\"\n                );\n            }\n        }\n\n        // While ``data-rel-reset=\"relative\"`` or just ``data-rel-reset``,\n        // ``data-rel-x/y/z`` and ``data-rel-rotate-x/y/z`` will have default value of 0,\n        // instead of inherit from previous slide.\n        //\n        // If ``data-rel-reset=\"all\"``, ``data-rotate-*`` are not inherited from previous slide too.\n        // So ``data-rel-reset=\"all\" data-rotate-x=\"90\"`` means\n        // ``data-rotate-x=\"90\" data-rotate-y=\"0\" data-rotate-z=\"0\"``, we doesn't need to\n        // bother clearing all unneeded attributes.\n\n        var inheritRotation = true;\n        if ( el.hasAttribute( \"data-rel-reset\" ) ) {\n\n            // Don't inherit from prev, just use the relative setting for current element\n            prev.relative = {\n                position: prev.relative.position,\n                x:0, y:0, z:0,\n                rotate: { x:0, y:0, z:0, order: \"xyz\" } };\n\n            if ( data.relReset === \"all\" ) {\n                inheritRotation = false;\n            }\n        }\n\n        var step = {\n                x: toNumberAdvanced( data.x, prev.x ),\n                y: toNumberAdvanced( data.y, prev.y ),\n                z: toNumberAdvanced( data.z, prev.z ),\n                rotate: {\n                    x: toNumber( data.rotateX, 0 ),\n                    y: toNumber( data.rotateY, 0 ),\n                    z: toNumber( data.rotateZ || data.rotate, 0 ),\n                    order: data.rotateOrder || \"xyz\"\n                },\n                relative: {\n                    position: data.relPosition || prev.relative.position,\n                    x: toNumberAdvanced( data.relX, prev.relative.x ),\n                    y: toNumberAdvanced( data.relY, prev.relative.y ),\n                    z: toNumberAdvanced( data.relZ, prev.relative.z ),\n                    rotate: {\n                        x: toNumber( data.relRotateX, prev.relative.rotate.x ),\n                        y: toNumber( data.relRotateY, prev.relative.rotate.y ),\n                        z: toNumber( data.relRotateZ, prev.relative.rotate.z ),\n                        order: data.rotateOrder || \"xyz\"\n                    }\n                }\n            };\n\n        // The final relatives maybe or maybe not the same with orignal data-rel-*\n        var relative = step.relative;\n\n        if ( step.relative.position === \"relative\" && inheritRotation ) {\n\n            // Calculate relatives based on previous slide\n            relative = api.lib.rotation.translateRelative(\n                step.relative, prev.rotate );\n\n            // Convert rotations to values that works with step.rotate\n            relative.rotate.x -= step.rotate.x;\n            relative.rotate.y -= step.rotate.y;\n            relative.rotate.z -= step.rotate.z;\n        }\n\n        // Relative position is ignored/zero if absolute is given.\n        // Note that this also has the effect of resetting any inherited relative values.\n        if ( data.x !== undefined ) {\n            relative.x = step.relative.x = 0;\n        }\n        if ( data.y !== undefined ) {\n            relative.y = step.relative.y = 0;\n        }\n        if ( data.z !== undefined ) {\n            relative.z = step.relative.z = 0;\n        }\n        if ( data.rotateX !== undefined || !inheritRotation ) {\n            relative.rotate.x = step.relative.rotate.x = 0;\n        }\n        if ( data.rotateY !== undefined || !inheritRotation ) {\n            relative.rotate.y = step.relative.rotate.y = 0;\n        }\n        if ( data.rotateZ !== undefined || data.rotate !== undefined || !inheritRotation ) {\n            relative.rotate.z = step.relative.rotate.z = 0;\n        }\n\n        step.x = step.x + relative.x;\n        step.y = step.y + relative.y;\n        step.z = step.z + relative.z;\n        step.rotate.x = step.rotate.x + relative.rotate.x;\n        step.rotate.y = step.rotate.y + relative.rotate.y;\n        step.rotate.z = step.rotate.z + relative.rotate.z;\n\n        return step;\n    };\n\n    var rel = function( root, impressApi ) {\n        api = impressApi;\n        toNumber = api.lib.util.toNumber;\n        toNumberAdvanced = api.lib.util.toNumberAdvanced;\n\n        var steps = root.querySelectorAll( \".step\" );\n        var prev;\n        startingState[ root.id ] = [];\n        for ( var i = 0; i < steps.length; i++ ) {\n            var el = steps[ i ];\n            startingState[ root.id ].push( {\n                el: el,\n                x: el.getAttribute( \"data-x\" ),\n                y: el.getAttribute( \"data-y\" ),\n                z: el.getAttribute( \"data-z\" ),\n                relX: el.getAttribute( \"data-rel-x\" ),\n                relY: el.getAttribute( \"data-rel-y\" ),\n                relZ: el.getAttribute( \"data-rel-z\" ),\n                rotateX: el.getAttribute( \"data-rotate-x\" ),\n                rotateY: el.getAttribute( \"data-rotate-y\" ),\n                rotateZ: el.getAttribute( \"data-rotate-z\" ),\n                rotate: el.getAttribute( \"data-rotate\" ),\n                relRotateX: el.getAttribute( \"data-rel-rotate-x\" ),\n                relRotateY: el.getAttribute( \"data-rel-rotate-y\" ),\n                relRotateZ: el.getAttribute( \"data-rel-rotate-z\" ),\n                relPosition: el.getAttribute( \"data-rel-position\" ),\n                rotateOrder: el.getAttribute( \"data-rotate-order\" )\n            } );\n            var step = computeRelativePositions( el, prev );\n\n            // Apply relative position (if non-zero)\n            el.setAttribute( \"data-x\", step.x );\n            el.setAttribute( \"data-y\", step.y );\n            el.setAttribute( \"data-z\", step.z );\n            el.setAttribute( \"data-rotate-x\", step.rotate.x );\n            el.setAttribute( \"data-rotate-y\", step.rotate.y );\n            el.setAttribute( \"data-rotate-z\", step.rotate.z );\n            el.setAttribute( \"data-rotate-order\", step.rotate.order );\n            el.setAttribute( \"data-rel-position\", step.relative.position );\n            el.setAttribute( \"data-rel-x\", step.relative.x );\n            el.setAttribute( \"data-rel-y\", step.relative.y );\n            el.setAttribute( \"data-rel-z\", step.relative.z );\n            el.setAttribute( \"data-rel-rotate-x\", step.relative.rotate.x );\n            el.setAttribute( \"data-rel-rotate-y\", step.relative.rotate.y );\n            el.setAttribute( \"data-rel-rotate-z\", step.relative.rotate.z );\n            prev = step;\n        }\n    };\n\n    // Register the plugin to be called in pre-init phase\n    window.impress.addPreInitPlugin( rel );\n\n    // Register teardown callback to reset the data.x, .y, .z values.\n    document.addEventListener( \"impress:init\", function( event ) {\n        var root = event.target;\n        event.detail.api.lib.gc.pushCallback( function() {\n            var steps = startingState[ root.id ];\n            var step;\n            var attrs = [\n                [ \"x\", \"relX\" ],\n                [ \"y\", \"relY\" ],\n                [ \"z\", \"relZ\" ],\n                [ \"rotate-x\", \"relRotateX\" ],\n                [ \"rotate-y\", \"relRotateY\" ],\n                [ \"rotate-z\", \"relRotateZ\" ],\n                [ \"rotate-order\", \"relRotateOrder\" ]\n            ];\n\n            while ( step = steps.pop() ) {\n\n                // Reset x/y/z in cases where this plugin has changed it.\n                for ( var i = 0; i < attrs.length; i++ ) {\n                    if ( step[ attrs[ i ][ 1 ] ] !== null ) {\n                        if ( step[ attrs[ i ][ 0 ] ] === null ) {\n                            step.el.removeAttribute( \"data-\" + attrs[ i ][ 0 ] );\n                        } else {\n                            step.el.setAttribute(\n                                \"data-\" + attrs[ i ][ 0 ], step[ attrs[ i ][ 0 ] ] );\n                        }\n                    }\n                }\n            }\n            delete startingState[ root.id ];\n        } );\n    }, false );\n} )( document, window );\n\n\n/**\n * Resize plugin\n *\n * Rescale the presentation after a window resize.\n *\n * Copyright 2011-2012 Bartek Szopka (@bartaz)\n * Released under the MIT license.\n * ------------------------------------------------\n *  author:  Bartek Szopka\n *  version: 0.5.3\n *  url:     http://bartaz.github.com/impress.js/\n *  source:  http://github.com/bartaz/impress.js/\n *\n */\n\n/* global document, window */\n\n( function( document, window ) {\n    \"use strict\";\n\n    // Wait for impress.js to be initialized\n    document.addEventListener( \"impress:init\", function( event ) {\n        var api = event.detail.api;\n\n        // Rescale presentation when window is resized\n        api.lib.gc.addEventListener( window, \"resize\", api.lib.util.throttle( function() {\n\n            // Force going to active step again, to trigger rescaling\n            api.goto( document.querySelector( \".step.active\" ), 500 );\n        }, 250 ), false );\n    }, false );\n\n} )( document, window );\n\n\n/**\n * Skip Plugin\n *\n * Example:\n *\n *    <!-- This slide is disabled in presentations, when moving with next()\n *         and prev() commands, but you can still move directly to it, for\n *         example with a url (anything using goto()). -->\n *         <div class=\"step skip\">\n *\n * Copyright 2016 Henrik Ingo (@henrikingo)\n * Released under the MIT license.\n */\n\n/* global document, window */\n\n( function( document, window ) {\n    \"use strict\";\n    var util;\n\n    document.addEventListener( \"impress:init\", function( event ) {\n        util = event.detail.api.lib.util;\n    }, false );\n\n    var getNextStep = function( el ) {\n        var steps = document.querySelectorAll( \".step\" );\n        for ( var i = 0; i < steps.length; i++ ) {\n            if ( steps[ i ] === el ) {\n                if ( i + 1 < steps.length ) {\n                    return steps[ i + 1 ];\n                } else {\n                    return steps[ 0 ];\n                }\n            }\n        }\n    };\n    var getPrevStep = function( el ) {\n        var steps = document.querySelectorAll( \".step\" );\n        for ( var i = steps.length - 1; i >= 0; i-- ) {\n            if ( steps[ i ] === el ) {\n                if ( i - 1 >= 0 ) {\n                    return steps[ i - 1 ];\n                } else {\n                    return steps[ steps.length - 1 ];\n                }\n            }\n        }\n    };\n\n    var skip = function( event ) {\n        if ( ( !event ) || ( !event.target ) ) {\n            return;\n        }\n\n        if ( event.detail.next.classList.contains( \"skip\" ) ) {\n            if ( event.detail.reason === \"next\" ) {\n\n                // Go to the next next step instead\n                event.detail.next = getNextStep( event.detail.next );\n\n                // Recursively call this plugin again, until there's a step not to skip\n                skip( event );\n            } else if ( event.detail.reason === \"prev\" ) {\n\n                // Go to the previous previous step instead\n                event.detail.next = getPrevStep( event.detail.next );\n                skip( event );\n            }\n\n            // If the new next element has its own transitionDuration, we're responsible for setting\n            // that on the event as well\n            event.detail.transitionDuration = util.toNumber(\n                event.detail.next.dataset.transitionDuration, event.detail.transitionDuration\n            );\n        }\n    };\n\n    // Register the plugin to be called in pre-stepleave phase\n    // The weight makes this plugin run early. This is a good thing, because this plugin calls\n    // itself recursively.\n    window.impress.addPreStepLeavePlugin( skip, 1 );\n\n} )( document, window );\n\n\n/**\n * Stop Plugin\n *\n * Example:\n *\n *        <!-- Stop at this slide.\n *             (For example, when used on the last slide, this prevents the\n *             presentation from wrapping back to the beginning.) -->\n *        <div class=\"step stop\">\n *\n * Copyright 2016 Henrik Ingo (@henrikingo)\n * Released under the MIT license.\n */\n/* global document, window */\n( function( document, window ) {\n    \"use strict\";\n\n    var stop = function( event ) {\n        if ( ( !event ) || ( !event.target ) ) {\n            return;\n        }\n\n        if ( event.target.classList.contains( \"stop\" ) ) {\n            if ( event.detail.reason === \"next\" ) {\n                return false;\n            }\n        }\n    };\n\n    // Register the plugin to be called in pre-stepleave phase\n    // The weight makes this plugin run fairly early.\n    window.impress.addPreStepLeavePlugin( stop, 2 );\n\n} )( document, window );\n\n\n/**\n * Substep Plugin\n *\n * Copyright 2017 Henrik Ingo (@henrikingo)\n * Released under the MIT license.\n */\n\n/* global document, window */\n\n( function( document, window ) {\n    \"use strict\";\n\n    // Copied from core impress.js. Good candidate for moving to src/lib/util.js.\n    var triggerEvent = function( el, eventName, detail ) {\n        var event = document.createEvent( \"CustomEvent\" );\n        event.initCustomEvent( eventName, true, true, detail );\n        el.dispatchEvent( event );\n    };\n\n    var activeStep = null;\n    document.addEventListener( \"impress:stepenter\", function( event ) {\n        activeStep = event.target;\n    }, false );\n\n    var substep = function( event ) {\n        if ( ( !event ) || ( !event.target ) ) {\n            return;\n        }\n\n        var step = event.target;\n        var el; // Needed by jshint\n        if ( event.detail.reason === \"next\" ) {\n            el = showSubstepIfAny( step );\n            if ( el ) {\n\n                // Send a message to others, that we aborted a stepleave event.\n                triggerEvent( step, \"impress:substep:stepleaveaborted\",\n                              { reason: \"next\", substep: el } );\n\n                // Autoplay uses this for reloading itself\n                triggerEvent( step, \"impress:substep:enter\",\n                              { reason: \"next\", substep: el } );\n\n                // Returning false aborts the stepleave event\n                return false;\n            }\n        }\n        if ( event.detail.reason === \"prev\" ) {\n            el = hideSubstepIfAny( step );\n            if ( el ) {\n                triggerEvent( step, \"impress:substep:stepleaveaborted\",\n                              { reason: \"prev\", substep: el } );\n\n                triggerEvent( step, \"impress:substep:leave\",\n                              { reason: \"prev\", substep: el } );\n\n                return false;\n            }\n        }\n    };\n\n    var showSubstepIfAny = function( step ) {\n        var substeps = step.querySelectorAll( \".substep\" );\n        if ( substeps.length > 0 ) {\n            var sorted = sortSubsteps( substeps );\n            var visible = step.querySelectorAll( \".substep-visible\" );\n            return showSubstep( sorted, visible );\n        }\n    };\n\n    var sortSubsteps = function( substepNodeList ) {\n        var substeps = Array.from( substepNodeList );\n        var sorted = substeps\n            .filter( el => el.dataset.substepOrder )\n            .sort( ( a, b ) => {\n                var orderA = a.dataset.substepOrder;\n                var orderB = b.dataset.substepOrder;\n                return parseInt( orderA ) - parseInt( orderB );\n            } )\n            .concat( substeps.filter( el => {\n                return el.dataset.substepOrder === undefined;\n            } ) );\n        return sorted;\n    };\n\n    var showSubstep = function( substeps, visible ) {\n        if ( visible.length < substeps.length ) {\n            for ( var i = 0; i < substeps.length; i++ ) {\n                substeps[ i ].classList.remove( \"substep-active\" );\n            }\n\n            // Loop over all substeps that are not yet visible and set\n            //   those of currentSubstepOrder to visible and active\n            var el;\n            var currentSubstepOrder;\n            for ( var j = visible.length; j < substeps.length; j++ ) {\n                if ( currentSubstepOrder &&\n                    currentSubstepOrder !== substeps[ j ].dataset.substepOrder ) {\n\n                    // Stop if the substepOrder is greater\n                    break;\n                }\n                el = substeps[ j ];\n                currentSubstepOrder = el.dataset.substepOrder;\n                el.classList.add( \"substep-visible\" );\n                el.classList.add( \"substep-active\" );\n                if ( currentSubstepOrder === undefined ) {\n\n                    // Stop after one substep as default order\n                    break;\n                }\n            }\n\n            return el;\n        }\n    };\n\n    var hideSubstepIfAny = function( step ) {\n        var substeps = step.querySelectorAll( \".substep\" );\n        var visible = step.querySelectorAll( \".substep-visible\" );\n        var sorted = sortSubsteps( visible );\n        if ( substeps.length > 0 ) {\n            return hideSubstep( sorted );\n        }\n    };\n\n    var hideSubstep = function( visible ) {\n        if ( visible.length > 0 ) {\n            var current = -1;\n            for ( var i = 0; i < visible.length; i++ ) {\n                if ( visible[ i ].classList.contains( \"substep-active\" ) ) {\n                    current = i;\n                }\n                visible[ i ].classList.remove( \"substep-active\" );\n            }\n            if ( current > 0 ) {\n                visible[ current - 1 ].classList.add( \"substep-active\" );\n            }\n            var el = visible[ visible.length - 1 ];\n            el.classList.remove( \"substep-visible\" );\n\n            // Continue if there is another substep with the same substepOrder\n            if ( current > 0 &&\n                visible[ current ].dataset.substepOrder !== undefined &&\n                visible[ current - 1 ].dataset.substepOrder ===\n                visible[ current ].dataset.substepOrder ) {\n                visible.pop();\n                return hideSubstep( visible );\n            }\n            return el;\n        }\n    };\n\n    // Register the plugin to be called in pre-stepleave phase.\n    // The weight makes this plugin run before other preStepLeave plugins.\n    window.impress.addPreStepLeavePlugin( substep, 1 );\n\n    // When entering a step, in particular when re-entering, make sure that all substeps are hidden\n    // at first\n    document.addEventListener( \"impress:stepenter\", function( event ) {\n        var step = event.target;\n        var visible = step.querySelectorAll( \".substep-visible\" );\n        for ( var i = 0; i < visible.length; i++ ) {\n            visible[ i ].classList.remove( \"substep-visible\" );\n        }\n    }, false );\n\n    // API for others to reveal/hide next substep ////////////////////////////////////////////////\n    document.addEventListener( \"impress:substep:show\", function() {\n        showSubstepIfAny( activeStep );\n    }, false );\n\n    document.addEventListener( \"impress:substep:hide\", function() {\n        hideSubstepIfAny( activeStep );\n    }, false );\n\n} )( document, window );\n\n/**\n * Support for swipe and tap on touch devices\n *\n * This plugin implements navigation for plugin devices, via swiping left/right,\n * or tapping on the left/right edges of the screen.\n *\n *\n *\n * Copyright 2015: Andrew Dunai (@and3rson)\n * Modified to a plugin, 2016: Henrik Ingo (@henrikingo)\n *\n * MIT License\n */\n/* global document, window */\n( function( document, window ) {\n    \"use strict\";\n\n    // Touch handler to detect swiping left and right based on window size.\n    // If the difference in X change is bigger than 1/20 of the screen width,\n    // we simply call an appropriate API function to complete the transition.\n    var startX = 0;\n    var lastX = 0;\n    var lastDX = 0;\n    var threshold = window.innerWidth / 20;\n\n    document.addEventListener( \"touchstart\", function( event ) {\n        lastX = startX = event.touches[ 0 ].clientX;\n    } );\n\n    document.addEventListener( \"touchmove\", function( event ) {\n         var x = event.touches[ 0 ].clientX;\n         var diff = x - startX;\n\n         // To be used in touchend\n         lastDX = lastX - x;\n         lastX = x;\n\n         window.impress().swipe( diff / window.innerWidth );\n     } );\n\n     document.addEventListener( \"touchend\", function() {\n         var totalDiff = lastX - startX;\n         if ( Math.abs( totalDiff ) > window.innerWidth / 5 && ( totalDiff * lastDX ) <= 0 ) {\n             if ( totalDiff > window.innerWidth / 5 && lastDX <= 0 ) {\n                 window.impress().prev();\n             } else if ( totalDiff < -window.innerWidth / 5 && lastDX >= 0 ) {\n                 window.impress().next();\n             }\n         } else if ( Math.abs( lastDX ) > threshold ) {\n             if ( lastDX < -threshold ) {\n                 window.impress().prev();\n             } else if ( lastDX > threshold ) {\n                 window.impress().next();\n             }\n         } else {\n\n             // No movement - move (back) to the current slide\n             window.impress().goto( document.querySelector( \"#impress .step.active\" ) );\n         }\n     } );\n\n     document.addEventListener( \"touchcancel\", function() {\n\n             // Move (back) to the current slide\n             window.impress().goto( document.querySelector( \"#impress .step.active\" ) );\n     } );\n\n} )( document, window );\n\n/**\n * Toolbar plugin\n *\n * This plugin provides a generic graphical toolbar. Other plugins that\n * want to expose a button or other widget, can add those to this toolbar.\n *\n * Using a single consolidated toolbar for all GUI widgets makes it easier\n * to position and style the toolbar rather than having to do that for lots\n * of different divs.\n *\n *\n * *** For presentation authors: *****************************************\n *\n * To add/activate the toolbar in your presentation, add this div:\n *\n *     <div id=\"impress-toolbar\"></div>\n *\n * Styling the toolbar is left to presentation author. Here's an example CSS:\n *\n *    .impress-enabled div#impress-toolbar {\n *        position: fixed;\n *        right: 1px;\n *        bottom: 1px;\n *        opacity: 0.6;\n *    }\n *    .impress-enabled div#impress-toolbar > span {\n *        margin-right: 10px;\n *    }\n *\n * The [mouse-timeout](../mouse-timeout/README.md) plugin can be leveraged to hide\n * the toolbar from sight, and only make it visible when mouse is moved.\n *\n *    body.impress-mouse-timeout div#impress-toolbar {\n *        display: none;\n *    }\n *\n *\n * *** For plugin authors **********************************************\n *\n * To add a button to the toolbar, trigger the `impress:toolbar:appendChild`\n * or `impress:toolbar:insertBefore` events as appropriate. The detail object\n * should contain following parameters:\n *\n *    { group : 1,                       // integer. Widgets with the same group are grouped inside\n *                                       // the same <span> element.\n *      html : \"<button>Click</button>\", // The html to add.\n *      callback : \"mycallback\",         // Toolbar plugin will trigger event\n *                                       // `impress:toolbar:added:mycallback` when done.\n *      before: element }                // The reference element for an insertBefore() call.\n *\n * You should also listen to the `impress:toolbar:added:mycallback` event. At\n * this point you can find the new widget in the DOM, and for example add an\n * event listener to it.\n *\n * You are free to use any integer for the group. It's ok to leave gaps. It's\n * ok to co-locate with widgets for another plugin, if you think they belong\n * together.\n *\n * See navigation-ui for an example.\n *\n * Copyright 2016 Henrik Ingo (@henrikingo)\n * Released under the MIT license.\n */\n\n/* global document */\n\n( function( document ) {\n    \"use strict\";\n    var toolbar = document.getElementById( \"impress-toolbar\" );\n    var groups = [];\n\n    /**\n     * Get the span element that is a child of toolbar, identified by index.\n     *\n     * If span element doesn't exist yet, it is created.\n     *\n     * Note: Because of Run-to-completion, this is not a race condition.\n     * https://developer.mozilla.org/en/docs/Web/JavaScript/EventLoop#Run-to-completion\n     *\n     * :param: index   Method will return the element <span id=\"impress-toolbar-group-{index}\">\n     */\n    var getGroupElement = function( index ) {\n        var id = \"impress-toolbar-group-\" + index;\n        if ( !groups[ index ] ) {\n            groups[ index ] = document.createElement( \"span\" );\n            groups[ index ].id = id;\n            var nextIndex = getNextGroupIndex( index );\n            if ( nextIndex === undefined ) {\n                toolbar.appendChild( groups[ index ] );\n            } else {\n                toolbar.insertBefore( groups[ index ], groups[ nextIndex ] );\n            }\n        }\n        return groups[ index ];\n    };\n\n    /**\n     * Get the span element from groups[] that is immediately after given index.\n     *\n     * This can be used to find the reference node for an insertBefore() call.\n     * If no element exists at a larger index, returns undefined. (In this case,\n     * you'd use appendChild() instead.)\n     *\n     * Note that index needn't itself exist in groups[].\n     */\n    var getNextGroupIndex = function( index ) {\n        var i = index + 1;\n        while ( !groups[ i ] && i < groups.length ) {\n            i++;\n        }\n        if ( i < groups.length ) {\n            return i;\n        }\n    };\n\n    // API\n    // Other plugins can add and remove buttons by sending them as events.\n    // In return, toolbar plugin will trigger events when button was added.\n    if ( toolbar ) {\n        /**\n         * Append a widget inside toolbar span element identified by given group index.\n         *\n         * :param: e.detail.group    integer specifying the span element where widget will be placed\n         * :param: e.detail.element  a dom element to add to the toolbar\n         */\n        toolbar.addEventListener( \"impress:toolbar:appendChild\", function( e ) {\n            var group = getGroupElement( e.detail.group );\n            group.appendChild( e.detail.element );\n        } );\n\n        /**\n         * Add a widget to toolbar using insertBefore() DOM method.\n         *\n         * :param: e.detail.before   the reference dom element, before which new element is added\n         * :param: e.detail.element  a dom element to add to the toolbar\n         */\n        toolbar.addEventListener( \"impress:toolbar:insertBefore\", function( e ) {\n            toolbar.insertBefore( e.detail.element, e.detail.before );\n        } );\n\n        /**\n         * Remove the widget in e.detail.remove.\n         */\n        toolbar.addEventListener( \"impress:toolbar:removeWidget\", function( e ) {\n            toolbar.removeChild( e.detail.remove );\n        } );\n\n        document.addEventListener( \"impress:init\", function( event ) {\n            var api = event.detail.api;\n            api.lib.gc.pushCallback( function() {\n                toolbar.innerHTML = \"\";\n                groups = [];\n            } );\n        } );\n    } // If toolbar\n\n} )( document );\n"
  },
  {
    "path": "karma.conf.js",
    "content": "// Karma configuration\n// Generated on Thu Feb 28 2019 16:31:36 GMT+0100 (Central European Standard Time)\nprocess.env.CHROME_BIN = require('puppeteer').executablePath()\n\nmodule.exports = function(config) {\n  config.set({\n\n    // base path that will be used to resolve all patterns (eg. files, exclude)\n    basePath: '',\n\n    hostname: '127.0.0.1',\n\n    // frameworks to use\n    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter\n    frameworks: ['qunit'],\n\n    plugins: ['karma-firefox-launcher', 'karma-chrome-launcher', 'karma-qunit'],\n\n    // List of files / patterns to load in the browser\n    files: [\n      // The QUnit tests\n      \"test/helpers.js\",\n      \"test/core_tests.js\",\n      \"test/non_default.js\",\n      \"src/plugins/navigation/navigation_tests.js\",\n      \"test/plugins/rel/relative_to_screen_size_tests.js\",\n      \"test/plugins/rel/rotation_tests.js\",\n      \"test/plugins/rel/padding_tests.js\",\n      \"test/plugins/rel/rel_to_tests.js\",\n      // Presentation files, for the iframe\n      {pattern: \"test/*.html\", watched: true, served: true, included: false},\n      {pattern: \"test/plugins/*/*.html\", watched: true, served: true, included: false},\n      // JS files for iframe\n      {pattern: \"js/impress.js\", watched: true, served: true, included: false},\n      {pattern: \"node_modules/qunit-assert-close/qunit-assert-close.js\", watched: false, served: true, included: true},\n      {pattern: \"node_modules/syn/dist/global/syn.js\", watched: false, served: true, included: false}\n    ],\n\n    proxies: {\n    \"/test/\": \"/base/test/\",\n    \"/js/\": \"/base/js/\",\n    \"/node_modules/\": \"/base/node_modules/\"\n  },\n    \n    client: {\n      clearContext: false,\n      qunit: {\n        showUI: true\n      }\n    },\n\n    // list of files / patterns to exclude\n    exclude: [\n    ],\n\n\n    // preprocess matching files before serving them to the browser\n    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor\n    preprocessors: {\n    },\n\n\n    // test results reporter to use\n    // possible values: 'dots', 'progress'\n    // available reporters: https://npmjs.org/browse/keyword/karma-reporter\n    reporters: ['progress'],\n\n\n    // web server port\n    port: 9876,\n\n\n    // enable / disable colors in the output (reporters and logs)\n    colors: true,\n\n\n    // level of logging\n    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG\n    logLevel: config.LOG_INFO,\n\n\n    // enable / disable watching file and executing tests whenever any file changes\n    autoWatch: true,\n\n\n    // start these browsers\n    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher\n    browsers: ['FirefoxHeadless', 'Chrome_without_security'],\n\n    customLaunchers: {\n      Chrome_without_security: {\n        base: 'ChromeHeadless',\n        flags: ['--no-sandbox']\n      }\n    },\n\n\n    // Continuous Integration mode\n    // if true, Karma captures browsers, runs the tests and exits\n    singleRun: false,\n\n    // Concurrency level\n    // how many browser should be started simultaneous\n    concurrency: Infinity\n  })\n}\n"
  },
  {
    "path": "npm-readme.md",
    "content": "# impress.js\n**What is impress.js?**\n\nimpress.js is a javascript framework that uses the power of CSS 3 transforms and transitions in today's browsers and is inspired by the idea behind [prezi.com](https://prezi.com).\n\n**WARNING**\n\nimpress.js may not help you if you have nothing interesting to say ;)\n\n\n# How to use it\nRun `npm i impress.js` to get the framework or head to our GitHub page and follow the instructions in the [README](/README.md) there. You may also use one of the links provided down below to include the script directly from a cdn. *Note:* This might not always work, so if it doesn't, just download the file and put it into a folder on your hard drive.\n\n## Direct links to only impress.js file\n- V2.0.0: https://cdn.jsdelivr.net/gh/impress/impress.js@2.0.0/js/impress.js\n- V1.1.0: https://cdn.jsdelivr.net/gh/impress/impress.js@1.1.0/js/impress.js\n- Source: https://cdn.jsdelivr.net/gh/impress/impress.js/js/impress.js\n\nFor older versions, please just replace the version number behind the @!\n\n## Getting Started guide\nAlternative download instructions that are more exhaustive are also included in our new [Getting Started](/GettingStarted.md) file where you can also get an introduction to impress.js.\n\n## Demos\nYou can see a demo of the framework in action [here](https://impress.js.org). Additional examples may be found [here](https://impress.js.org/examples).\n\n# Browser Support\nThe design goal for impress.js has been to showcase awesome CSS3 features as found in modern browser versions. We also use some new DOM functionality, and specifically do not use jQuery or any other JavaScript libraries, nor our own functions, to support older browsers. In general, recent versions of Firefox and Chrome are known to work well. Reportedly IE now works too.\n\nThe typical use case for impress.js is to create presentations that you present from your own laptop, with a browser version you know works well. Some people also use impress.js successfully to embed animations or presentations in a web page, however, be aware that in this some of your visitors may not see the presentation correctly, or at all.\n\nIn particular, impress.js makes use of the following JS and CSS features:\n\n- [DataSet API](http://caniuse.com/#search=dataset)\n- [ClassList API](http://caniuse.com/#search=classlist)\n- [CSS 3D Transforms](http://caniuse.com/#search=css%203d)\n- [CSS Transitions](http://caniuse.com/#search=css%20transition)\n\n# COPYRIGHT AND LICENSE\nCopyright 2011-2016 Bartek Szopka\n\nCopyright 2015-present Henrik Ingo\n\nReleased under the MIT [License](https://github.com/impress/impress.js/blob/HEAD/LICENSE)\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"impress.js\",\n  \"version\": \"1.1.0\",\n  \"description\": \"It's a presentation framework based on the power of CSS3 transforms and transitions in modern browsers and inspired by the idea behind prezi.com.\",\n  \"main\": \"js/impress.js\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/impress/impress.js.git\"\n  },\n  \"keywords\": [\n    \"presentation\",\n    \"slides\",\n    \"slideshow\",\n    \"css3\",\n    \"transitions\",\n    \"transforms\",\n    \"browser\"\n  ],\n  \"author\": \"Bartek Szopka\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/impress/impress.js/issues\"\n  },\n  \"scripts\": {\n    \"all\": \"npm run build && npm run test && npm run lint\",\n    \"build\": \"node build.js\",\n    \"lint\": \"npm exec -- jshint src test/*.js && npm exec -- jscs src test/*.js\",\n    \"new-lint\": \"npm exec -- eslint src test\",\n    \"test\": \"npm exec -- karma start --log-level debug --single-run=true\"\n  },\n  \"devDependencies\": {\n    \"eslint\": \"^6.8.0\",\n    \"jscs\": \"^3.0.7\",\n    \"jshint\": \"^2.11.0\",\n    \"karma\": \"^4.4.1\",\n    \"karma-chrome-launcher\": \"^3.1.0\",\n    \"karma-firefox-launcher\": \"^1.3.0\",\n    \"karma-qunit\": \"^4.0.0\",\n    \"ls\": \"^0.2.1\",\n    \"puppeteer\": \"^2.1.1\",\n    \"qunit\": \"^2.9.3\",\n    \"qunit-assert-close\": \"^2.1.2\",\n    \"syn\": \"^0.14.1\",\n    \"terser\": \"^4.6.7\"\n  }\n}\n"
  },
  {
    "path": "qunit_test_runner.html",
    "content": "<!DOCTYPE html>\n  <!--\n  Copyright 2016 Henrik Ingo (@henrikingo)\n  Released under the MIT license. See LICENSE file.\n  -->\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>QUnit tests for impress.js</title>\n  <link rel=\"stylesheet\" href=\"node_modules/qunit/qunit/qunit.css\">\n</head>\n<body>\n  <div id=\"qunit\"></div>\n  <div id=\"qunit-fixture\"></div>\n  <script src=\"node_modules/qunit/qunit/qunit.js\"></script>\n  <script src=\"node_modules/qunit-assert-close/qunit-assert-close.js\"></script>\n  <!-- The QUnit tests. -->\n  <script src=\"test/helpers.js\"></script>\n  <!-- Core tests -->\n  <script src=\"test/core_tests.js\"></script>\n  <script src=\"test/non_default.js\"></script>\n  <!-- Plugins -->\n  <script src=\"src/plugins/navigation/navigation_tests.js\"></script>\n  <script src=\"test/plugins/rel/relative_to_screen_size_tests.js\"></script>\n  <script src=\"test/plugins/rel/rotation_tests.js\"></script>\n  <script src=\"test/plugins/rel/padding_tests.js\"></script>\n  <script src=\"test/plugins/rel/rel_to_tests.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "src/impress.js",
    "content": "/*! Licensed under MIT License - http://github.com/impress/impress.js */\n/**\n * impress.js\n *\n * impress.js is a presentation tool based on the power of CSS3 transforms and transitions\n * in modern browsers and inspired by the idea behind prezi.com.\n *\n *\n * Copyright 2011-2012 Bartek Szopka (@bartaz), 2016-2023 Henrik Ingo (@henrikingo)\n * and 70+ other contributors\n *\n * Released under the MIT License.\n *\n * ------------------------------------------------\n *  author:  Bartek Szopka, Henrik Ingo\n *  version: 2.0.0\n *  url:     http://impress.js.org\n *  source:  http://github.com/impress/impress.js/\n */\n\n// You are one of those who like to know how things work inside?\n// Let me show you the cogs that make impress.js run...\n( function( document, window ) {\n    \"use strict\";\n    var lib;\n\n    // HELPER FUNCTIONS\n\n    // `pfx` is a function that takes a standard CSS property name as a parameter\n    // and returns it's prefixed version valid for current browser it runs in.\n    // The code is heavily inspired by Modernizr http://www.modernizr.com/\n    var pfx = ( function() {\n\n        var style = document.createElement( \"dummy\" ).style,\n            prefixes = \"Webkit Moz O ms Khtml\".split( \" \" ),\n            memory = {};\n\n        return function( prop ) {\n            if ( typeof memory[ prop ] === \"undefined\" ) {\n\n                var ucProp  = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ),\n                    props   = ( prop + \" \" + prefixes.join( ucProp + \" \" ) + ucProp ).split( \" \" );\n\n                memory[ prop ] = null;\n                for ( var i in props ) {\n                    if ( style[ props[ i ] ] !== undefined ) {\n                        memory[ prop ] = props[ i ];\n                        break;\n                    }\n                }\n\n            }\n\n            return memory[ prop ];\n        };\n\n    } )();\n\n    var validateOrder = function( order, fallback ) {\n        var validChars = \"xyz\";\n        var returnStr = \"\";\n        if ( typeof order === \"string\" ) {\n            for ( var i in order.split( \"\" ) ) {\n                if ( validChars.indexOf( order[ i ] ) >= 0 ) {\n                    returnStr += order[ i ];\n\n                    // Each of x,y,z can be used only once.\n                    validChars = validChars.split( order[ i ] ).join( \"\" );\n                }\n            }\n        }\n        if ( returnStr ) {\n            return returnStr;\n        } else if ( fallback !== undefined ) {\n            return fallback;\n        } else {\n            return \"xyz\";\n        }\n    };\n\n    // `css` function applies the styles given in `props` object to the element\n    // given as `el`. It runs all property names through `pfx` function to make\n    // sure proper prefixed version of the property is used.\n    var css = function( el, props ) {\n        var key, pkey;\n        for ( key in props ) {\n            if ( props.hasOwnProperty( key ) ) {\n                pkey = pfx( key );\n                if ( pkey !== null ) {\n                    el.style[ pkey ] = props[ key ];\n                }\n            }\n        }\n        return el;\n    };\n\n    // `translate` builds a translate transform string for given data.\n    var translate = function( t ) {\n        return \" translate3d(\" + t.x + \"px,\" + t.y + \"px,\" + t.z + \"px) \";\n    };\n\n    // `rotate` builds a rotate transform string for given data.\n    // By default the rotations are in X Y Z order that can be reverted by passing `true`\n    // as second parameter.\n    var rotate = function( r, revert ) {\n        var order = r.order ? r.order : \"xyz\";\n        var css = \"\";\n        var axes = order.split( \"\" );\n        if ( revert ) {\n            axes = axes.reverse();\n        }\n\n        for ( var i = 0; i < axes.length; i++ ) {\n            css += \" rotate\" + axes[ i ].toUpperCase() + \"(\" + r[ axes[ i ] ] + \"deg)\";\n        }\n        return css;\n    };\n\n    // `scale` builds a scale transform string for given data.\n    var scale = function( s ) {\n        return \" scale(\" + s + \") \";\n    };\n\n    // `computeWindowScale` counts the scale factor between window size and size\n    // defined for the presentation in the config.\n    var computeWindowScale = function( config ) {\n        var hScale = window.innerHeight / config.height,\n            wScale = window.innerWidth / config.width,\n            scale = hScale > wScale ? wScale : hScale;\n\n        if ( config.maxScale && scale > config.maxScale ) {\n            scale = config.maxScale;\n        }\n\n        if ( config.minScale && scale < config.minScale ) {\n            scale = config.minScale;\n        }\n\n        return scale;\n    };\n\n    // CHECK SUPPORT\n    var body = document.body;\n    var impressSupported =\n\n                          // Browser should support CSS 3D transtorms\n                           ( pfx( \"perspective\" ) !== null ) &&\n\n                          // And `classList` and `dataset` APIs\n                           ( body.classList ) &&\n                           ( body.dataset );\n\n    if ( !impressSupported ) {\n\n        // We can't be sure that `classList` is supported\n        body.className += \" impress-not-supported \";\n    }\n\n    // GLOBALS AND DEFAULTS\n\n    // This is where the root elements of all impress.js instances will be kept.\n    // Yes, this means you can have more than one instance on a page, but I'm not\n    // sure if it makes any sense in practice ;)\n    var roots = {};\n\n    var preInitPlugins = [];\n    var preStepLeavePlugins = [];\n\n    // Some default config values.\n    var defaults = {\n        width: 1920,\n        height: 1080,\n        maxScale: 3,\n        minScale: 0,\n\n        perspective: 1000,\n\n        transitionDuration: 1000\n    };\n\n    // Configuration options\n    var config = null;\n\n    // It's just an empty function ... and a useless comment.\n    var empty = function() { return false; };\n\n    // IMPRESS.JS API\n\n    // And that's where interesting things will start to happen.\n    // It's the core `impress` function that returns the impress.js API\n    // for a presentation based on the element with given id (\"impress\"\n    // by default).\n    var impress = window.impress = function( rootId ) {\n\n        // If impress.js is not supported by the browser return a dummy API\n        // it may not be a perfect solution but we return early and avoid\n        // running code that may use features not implemented in the browser.\n        if ( !impressSupported ) {\n            return {\n                init: empty,\n                goto: empty,\n                prev: empty,\n                next: empty,\n                swipe: empty,\n                tear: empty,\n                lib: {}\n            };\n        }\n\n        rootId = rootId || \"impress\";\n\n        // If given root is already initialized just return the API\n        if ( roots[ \"impress-root-\" + rootId ] ) {\n            return roots[ \"impress-root-\" + rootId ];\n        }\n\n        // The gc library depends on being initialized before we do any changes to DOM.\n        lib = initLibraries( rootId );\n\n        body.classList.remove( \"impress-not-supported\" );\n        body.classList.add( \"impress-supported\" );\n\n        // Data of all presentation steps\n        var stepsData = {};\n\n        // Element of currently active step\n        var activeStep = null;\n\n        // Current state (position, rotation and scale) of the presentation\n        var currentState = null;\n\n        // Array of step elements\n        var steps = null;\n\n        // Scale factor of the browser window\n        var windowScale = null;\n\n        // Root presentation elements\n        var root = lib.util.byId( rootId );\n        var canvas = document.createElement( \"div\" );\n\n        var initialized = false;\n\n        // STEP EVENTS\n        //\n        // There are currently two step events triggered by impress.js\n        // `impress:stepenter` is triggered when the step is shown on the\n        // screen (the transition from the previous one is finished) and\n        // `impress:stepleave` is triggered when the step is left (the\n        // transition to next step just starts).\n\n        // Reference to last entered step\n        var lastEntered = null;\n\n        // `onStepEnter` is called whenever the step element is entered\n        // but the event is triggered only if the step is different than\n        // last entered step.\n        // We sometimes call `goto`, and therefore `onStepEnter`, just to redraw a step, such as\n        // after screen resize. In this case - more precisely, in any case - we trigger a\n        // `impress:steprefresh` event.\n        var onStepEnter = function( step ) {\n            if ( lastEntered !== step ) {\n                lib.util.triggerEvent( step, \"impress:stepenter\" );\n                lastEntered = step;\n            }\n            lib.util.triggerEvent( step, \"impress:steprefresh\" );\n        };\n\n        // `onStepLeave` is called whenever the currentStep element is left\n        // but the event is triggered only if the currentStep is the same as\n        // lastEntered step.\n        var onStepLeave = function( currentStep, nextStep ) {\n            if ( lastEntered === currentStep ) {\n                lib.util.triggerEvent( currentStep, \"impress:stepleave\", { next: nextStep } );\n                lastEntered = null;\n            }\n        };\n\n        // `initStep` initializes given step element by reading data from its\n        // data attributes and setting correct styles.\n        var initStep = function( el, idx ) {\n            var data = el.dataset,\n                step = {\n                    translate: {\n                        x: lib.util.toNumberAdvanced( data.x ),\n                        y: lib.util.toNumberAdvanced( data.y ),\n                        z: lib.util.toNumberAdvanced( data.z )\n                    },\n                    rotate: {\n                        x: lib.util.toNumber( data.rotateX ),\n                        y: lib.util.toNumber( data.rotateY ),\n                        z: lib.util.toNumber( data.rotateZ || data.rotate ),\n                        order: validateOrder( data.rotateOrder )\n                    },\n                    scale: lib.util.toNumber( data.scale, 1 ),\n                    transitionDuration: lib.util.toNumber(\n                        data.transitionDuration, config.transitionDuration\n                    ),\n                    el: el\n                };\n\n            if ( !el.id ) {\n                el.id = \"step-\" + ( idx + 1 );\n            }\n\n            stepsData[ \"impress-\" + el.id ] = step;\n\n            css( el, {\n                position: \"absolute\",\n                transform: \"translate(-50%,-50%)\" +\n                           translate( step.translate ) +\n                           rotate( step.rotate ) +\n                           scale( step.scale ),\n                transformStyle: \"preserve-3d\"\n            } );\n        };\n\n        // Initialize all steps.\n        // Read the data-* attributes, store in internal stepsData, and render with CSS.\n        var initAllSteps = function() {\n            steps = lib.util.$$( \".step\", root );\n            steps.forEach( initStep );\n        };\n\n        // Build configuration from root and defaults\n        var buildConfig = function() {\n            var rootData = root.dataset;\n            return {\n                width: lib.util.toNumber( rootData.width, defaults.width ),\n                height: lib.util.toNumber( rootData.height, defaults.height ),\n                maxScale: lib.util.toNumber( rootData.maxScale, defaults.maxScale ),\n                minScale: lib.util.toNumber( rootData.minScale, defaults.minScale ),\n                perspective: lib.util.toNumber( rootData.perspective, defaults.perspective ),\n                transitionDuration: lib.util.toNumber(\n                    rootData.transitionDuration, defaults.transitionDuration\n                )\n            };\n        };\n\n        // `init` API function that initializes (and runs) the presentation.\n        var init = function() {\n            if ( initialized ) { return; }\n\n            // Initialize the configuration object, so it can be used by pre-init plugins.\n            config = buildConfig();\n            execPreInitPlugins( root );\n\n            // First we set up the viewport for mobile devices.\n            // For some reason iPad goes nuts when it is not done properly.\n            var meta = lib.util.$( \"meta[name='viewport']\" ) || document.createElement( \"meta\" );\n            meta.content = \"width=device-width, minimum-scale=1, maximum-scale=1, user-scalable=no\";\n            if ( meta.parentNode !== document.head ) {\n                meta.name = \"viewport\";\n                document.head.appendChild( meta );\n            }\n\n            windowScale = computeWindowScale( config );\n\n            // Wrap steps with \"canvas\" element\n            lib.util.arrayify( root.childNodes ).forEach( function( el ) {\n                canvas.appendChild( el );\n            } );\n            root.appendChild( canvas );\n\n            // Set initial styles\n            document.documentElement.style.height = \"100%\";\n\n            css( body, {\n                height: \"100%\",\n                overflow: \"hidden\"\n            } );\n\n            var rootStyles = {\n                position: \"absolute\",\n                transformOrigin: \"top left\",\n                transition: \"all 0s ease-in-out\",\n                transformStyle: \"preserve-3d\"\n            };\n\n            css( root, rootStyles );\n            css( root, {\n                top: \"50%\",\n                left: \"50%\",\n                perspective: ( config.perspective / windowScale ) + \"px\",\n                transform: scale( windowScale )\n            } );\n            css( canvas, rootStyles );\n\n            body.classList.remove( \"impress-disabled\" );\n            body.classList.add( \"impress-enabled\" );\n\n            // Get and init steps\n            initAllSteps();\n\n            // Set a default initial state of the canvas\n            currentState = {\n                translate: { x: 0, y: 0, z: 0 },\n                rotate:    { x: 0, y: 0, z: 0, order: \"xyz\" },\n                scale:     1\n            };\n\n            initialized = true;\n\n            lib.util.triggerEvent( root, \"impress:init\",\n                                   { api: roots[ \"impress-root-\" + rootId ] } );\n        };\n\n        // `getStep` is a helper function that returns a step element defined by parameter.\n        // If a number is given, step with index given by the number is returned, if a string\n        // is given step element with such id is returned, if DOM element is given it is returned\n        // if it is a correct step element.\n        var getStep = function( step ) {\n            if ( typeof step === \"number\" ) {\n                step = step < 0 ? steps[ steps.length + step ] : steps[ step ];\n            } else if ( typeof step === \"string\" ) {\n                step = lib.util.byId( step );\n            }\n            return ( step && step.id && stepsData[ \"impress-\" + step.id ] ) ? step : null;\n        };\n\n        // Used to reset timeout for `impress:stepenter` event\n        var stepEnterTimeout = null;\n\n        // `goto` API function that moves to step given as `el` parameter (by index, id or element).\n        // `duration` optionally given as second parameter, is the transition duration in css.\n        // `reason` is the string \"next\", \"prev\" or \"goto\" (default) and will be made available to\n        // preStepLeave plugins.\n        // `origEvent` may contain event that caused the call to goto, such as a key press event\n        var goto = function( el, duration, reason, origEvent ) {\n            reason = reason || \"goto\";\n            origEvent = origEvent || null;\n\n            if ( !initialized ) {\n                return false;\n            }\n\n            // Re-execute initAllSteps for each transition. This allows to edit step attributes\n            // dynamically, such as change their coordinates, or even remove or add steps, and have\n            // that change apply when goto() is called.\n            initAllSteps();\n\n            if ( !( el = getStep( el ) ) ) {\n                return false;\n            }\n\n            // Sometimes it's possible to trigger focus on first link with some keyboard action.\n            // Browser in such a case tries to scroll the page to make this element visible\n            // (even that body overflow is set to hidden) and it breaks our careful positioning.\n            //\n            // So, as a lousy (and lazy) workaround we will make the page scroll back to the top\n            // whenever slide is selected\n            //\n            // If you are reading this and know any better way to handle it, I'll be glad to hear\n            // about it!\n            window.scrollTo( 0, 0 );\n\n            var step = stepsData[ \"impress-\" + el.id ];\n            duration = ( duration !== undefined ? duration : step.transitionDuration );\n\n            // If we are in fact moving to another step, start with executing the registered\n            // preStepLeave plugins.\n            if ( activeStep && activeStep !== el ) {\n                var event = { target: activeStep, detail: {} };\n                event.detail.next = el;\n                event.detail.transitionDuration = duration;\n                event.detail.reason = reason;\n                if ( origEvent ) {\n                    event.origEvent = origEvent;\n                }\n\n                if ( execPreStepLeavePlugins( event ) === false ) {\n\n                    // PreStepLeave plugins are allowed to abort the transition altogether, by\n                    // returning false.\n                    // see stop and substep plugins for an example of doing just that\n                    return false;\n                }\n\n                // Plugins are allowed to change the detail values\n                el = event.detail.next;\n                step = stepsData[ \"impress-\" + el.id ];\n                duration = event.detail.transitionDuration;\n            }\n\n            if ( activeStep ) {\n                activeStep.classList.remove( \"active\" );\n                body.classList.remove( \"impress-on-\" + activeStep.id );\n            }\n            el.classList.add( \"active\" );\n\n            body.classList.add( \"impress-on-\" + el.id );\n\n            // Compute target state of the canvas based on given step\n            var target = {\n                rotate: {\n                    x: -step.rotate.x,\n                    y: -step.rotate.y,\n                    z: -step.rotate.z,\n                    order: step.rotate.order\n                },\n                translate: {\n                    x: -step.translate.x,\n                    y: -step.translate.y,\n                    z: -step.translate.z\n                },\n                scale: 1 / step.scale\n            };\n\n            // Check if the transition is zooming in or not.\n            //\n            // This information is used to alter the transition style:\n            // when we are zooming in - we start with move and rotate transition\n            // and the scaling is delayed, but when we are zooming out we start\n            // with scaling down and move and rotation are delayed.\n            var zoomin = target.scale >= currentState.scale;\n\n            duration = lib.util.toNumber( duration, config.transitionDuration );\n            var delay = ( duration / 2 );\n\n            // If the same step is re-selected, force computing window scaling,\n            // because it is likely to be caused by window resize\n            if ( el === activeStep ) {\n                windowScale = computeWindowScale( config );\n            }\n\n            var targetScale = target.scale * windowScale;\n\n            // Trigger leave of currently active element (if it's not the same step again)\n            if ( activeStep && activeStep !== el ) {\n                onStepLeave( activeStep, el );\n            }\n\n            // Now we alter transforms of `root` and `canvas` to trigger transitions.\n            //\n            // And here is why there are two elements: `root` and `canvas` - they are\n            // being animated separately:\n            // `root` is used for scaling and `canvas` for translate and rotations.\n            // Transitions on them are triggered with different delays (to make\n            // visually nice and \"natural\" looking transitions), so we need to know\n            // that both of them are finished.\n            css( root, {\n\n                // To keep the perspective look similar for different scales\n                // we need to \"scale\" the perspective, too\n                // For IE 11 support we must specify perspective independent\n                // of transform.\n                perspective: ( config.perspective / targetScale ) + \"px\",\n                transform: scale( targetScale ),\n                transitionDuration: duration + \"ms\",\n                transitionDelay: ( zoomin ? delay : 0 ) + \"ms\"\n            } );\n\n            css( canvas, {\n                transform: rotate( target.rotate, true ) + translate( target.translate ),\n                transitionDuration: duration + \"ms\",\n                transitionDelay: ( zoomin ? 0 : delay ) + \"ms\"\n            } );\n\n            // Here is a tricky part...\n            //\n            // If there is no change in scale or no change in rotation and translation, it means\n            // there was actually no delay - because there was no transition on `root` or `canvas`\n            // elements. We want to trigger `impress:stepenter` event in the correct moment, so\n            // here we compare the current and target values to check if delay should be taken into\n            // account.\n            //\n            // I know that this `if` statement looks scary, but it's pretty simple when you know\n            // what is going on - it's simply comparing all the values.\n            if ( currentState.scale === target.scale ||\n                ( currentState.rotate.x === target.rotate.x &&\n                  currentState.rotate.y === target.rotate.y &&\n                  currentState.rotate.z === target.rotate.z &&\n                  currentState.translate.x === target.translate.x &&\n                  currentState.translate.y === target.translate.y &&\n                  currentState.translate.z === target.translate.z ) ) {\n                delay = 0;\n            }\n\n            // Store current state\n            currentState = target;\n            activeStep = el;\n\n            // And here is where we trigger `impress:stepenter` event.\n            // We simply set up a timeout to fire it taking transition duration (and possible delay)\n            // into account.\n            //\n            // I really wanted to make it in more elegant way. The `transitionend` event seemed to\n            // be the best way to do it, but the fact that I'm using transitions on two separate\n            // elements and that the `transitionend` event is only triggered when there was a\n            // transition (change in the values) caused some bugs and made the code really\n            // complicated, cause I had to handle all the conditions separately. And it still\n            // needed a `setTimeout` fallback for the situations when there is no transition at all.\n            // So I decided that I'd rather make the code simpler than use shiny new\n            // `transitionend`.\n            //\n            // If you want learn something interesting and see how it was done with `transitionend`\n            // go back to version 0.5.2 of impress.js:\n            // http://github.com/bartaz/impress.js/blob/0.5.2/js/impress.js\n            window.clearTimeout( stepEnterTimeout );\n            stepEnterTimeout = window.setTimeout( function() {\n                onStepEnter( activeStep );\n            }, duration + delay );\n\n            return el;\n        };\n\n        // `prev` API function goes to previous step (in document order)\n        // `event` is optional, may contain the event that caused the need to call prev()\n        var prev = function( origEvent ) {\n            var prev = steps.indexOf( activeStep ) - 1;\n            prev = prev >= 0 ? steps[ prev ] : steps[ steps.length - 1 ];\n\n            return goto( prev, undefined, \"prev\", origEvent );\n        };\n\n        // `next` API function goes to next step (in document order)\n        // `event` is optional, may contain the event that caused the need to call next()\n        var next = function( origEvent ) {\n            var next = steps.indexOf( activeStep ) + 1;\n            next = next < steps.length ? steps[ next ] : steps[ 0 ];\n\n            return goto( next, undefined, \"next\", origEvent );\n        };\n\n        // Swipe for touch devices by @and3rson.\n        // Below we extend the api to control the animation between the currently\n        // active step and a presumed next/prev step. See touch plugin for\n        // an example of using this api.\n\n        // Helper function\n        var interpolate = function( a, b, k ) {\n            return a + ( b - a ) * k;\n        };\n\n        // Animate a swipe.\n        //\n        // Pct is a value between -1.0 and +1.0, designating the current length\n        // of the swipe.\n        //\n        // If pct is negative, swipe towards the next() step, if positive,\n        // towards the prev() step.\n        //\n        // Note that pre-stepleave plugins such as goto can mess with what is a\n        // next() and prev() step, so we need to trigger the pre-stepleave event\n        // here, even if a swipe doesn't guarantee that the transition will\n        // actually happen.\n        //\n        // Calling swipe(), with any value of pct, won't in itself cause a\n        // transition to happen, this is just to animate the swipe. Once the\n        // transition is committed - such as at a touchend event - caller is\n        // responsible for also calling prev()/next() as appropriate.\n        //\n        // Note: For now, this function is made available to be used by the swipe plugin (which\n        // is the UI counterpart to this). It is a semi-internal API and intentionally not\n        // documented in DOCUMENTATION.md.\n        var swipe = function( pct ) {\n            if ( Math.abs( pct ) > 1 ) {\n                return;\n            }\n\n            // Prepare & execute the preStepLeave event\n            var event = { target: activeStep, detail: {} };\n            event.detail.swipe = pct;\n\n            // Will be ignored within swipe animation, but just in case a plugin wants to read this,\n            // humor them\n            event.detail.transitionDuration = config.transitionDuration;\n            var idx; // Needed by jshint\n            if ( pct < 0 ) {\n                idx = steps.indexOf( activeStep ) + 1;\n                event.detail.next = idx < steps.length ? steps[ idx ] : steps[ 0 ];\n                event.detail.reason = \"next\";\n            } else if ( pct > 0 ) {\n                idx = steps.indexOf( activeStep ) - 1;\n                event.detail.next = idx >= 0 ? steps[ idx ] : steps[ steps.length - 1 ];\n                event.detail.reason = \"prev\";\n            } else {\n\n                // No move\n                return;\n            }\n            if ( execPreStepLeavePlugins( event ) === false ) {\n\n                // If a preStepLeave plugin wants to abort the transition, don't animate a swipe\n                // For stop, this is probably ok. For substep, the plugin it self might want to do\n                // some animation, but that's not the current implementation.\n                return false;\n            }\n            var nextElement = event.detail.next;\n\n            var nextStep = stepsData[ \"impress-\" + nextElement.id ];\n\n            // If the same step is re-selected, force computing window scaling,\n            var nextScale = nextStep.scale * windowScale;\n            var k = Math.abs( pct );\n\n            var interpolatedStep = {\n                translate: {\n                    x: interpolate( currentState.translate.x, -nextStep.translate.x, k ),\n                    y: interpolate( currentState.translate.y, -nextStep.translate.y, k ),\n                    z: interpolate( currentState.translate.z, -nextStep.translate.z, k )\n                },\n                rotate: {\n                    x: interpolate( currentState.rotate.x, -nextStep.rotate.x, k ),\n                    y: interpolate( currentState.rotate.y, -nextStep.rotate.y, k ),\n                    z: interpolate( currentState.rotate.z, -nextStep.rotate.z, k ),\n\n                    // Unfortunately there's a discontinuity if rotation order changes. Nothing I\n                    // can do about it?\n                    order: k < 0.7 ? currentState.rotate.order : nextStep.rotate.order\n                },\n                scale: interpolate( currentState.scale * windowScale, nextScale, k )\n            };\n\n            css( root, {\n\n                // To keep the perspective look similar for different scales\n                // we need to 'scale' the perspective, too\n                perspective: config.perspective / interpolatedStep.scale + \"px\",\n                transform: scale( interpolatedStep.scale ),\n                transitionDuration: \"0ms\",\n                transitionDelay: \"0ms\"\n            } );\n\n            css( canvas, {\n                transform: rotate( interpolatedStep.rotate, true ) +\n                           translate( interpolatedStep.translate ),\n                transitionDuration: \"0ms\",\n                transitionDelay: \"0ms\"\n            } );\n        };\n\n        // Teardown impress\n        // Resets the DOM to the state it was before impress().init() was called.\n        // (If you called impress(rootId).init() for multiple different rootId's, then you must\n        // also call tear() once for each of them.)\n        var tear = function() {\n            lib.gc.teardown();\n            delete roots[ \"impress-root-\" + rootId ];\n        };\n\n        // Adding some useful classes to step elements.\n        //\n        // All the steps that have not been shown yet are given `future` class.\n        // When the step is entered the `future` class is removed and the `present`\n        // class is given. When the step is left `present` class is replaced with\n        // `past` class.\n        //\n        // So every step element is always in one of three possible states:\n        // `future`, `present` and `past`.\n        //\n        // There classes can be used in CSS to style different types of steps.\n        // For example the `present` class can be used to trigger some custom\n        // animations when step is shown.\n        lib.gc.addEventListener( root, \"impress:init\", function() {\n\n            // STEP CLASSES\n            steps.forEach( function( step ) {\n                step.classList.add( \"future\" );\n            } );\n\n            lib.gc.addEventListener( root, \"impress:stepenter\", function( event ) {\n                event.target.classList.remove( \"past\" );\n                event.target.classList.remove( \"future\" );\n                event.target.classList.add( \"present\" );\n            }, false );\n\n            lib.gc.addEventListener( root, \"impress:stepleave\", function( event ) {\n                event.target.classList.remove( \"present\" );\n                event.target.classList.add( \"past\" );\n            }, false );\n\n        }, false );\n\n        // Adding hash change support.\n        lib.gc.addEventListener( root, \"impress:init\", function() {\n\n            // Last hash detected\n            var lastHash = \"\";\n\n            // `#/step-id` is used instead of `#step-id` to prevent default browser\n            // scrolling to element in hash.\n            //\n            // And it has to be set after animation finishes, because in Chrome it\n            // makes transition laggy.\n            // BUG: http://code.google.com/p/chromium/issues/detail?id=62820\n            lib.gc.addEventListener( root, \"impress:stepenter\", function( event ) {\n                window.location.hash = lastHash = \"#/\" + event.target.id;\n            }, false );\n\n            lib.gc.addEventListener( window, \"hashchange\", function() {\n\n                // When the step is entered hash in the location is updated\n                // (just few lines above from here), so the hash change is\n                // triggered and we would call `goto` again on the same element.\n                //\n                // To avoid this we store last entered hash and compare.\n                if ( window.location.hash !== lastHash ) {\n                    goto( lib.util.getElementFromHash() );\n                }\n            }, false );\n\n            // START\n            // by selecting step defined in url or first step of the presentation\n            goto( lib.util.getElementFromHash() || steps[ 0 ], 0 );\n        }, false );\n\n        body.classList.add( \"impress-disabled\" );\n\n        // Store and return API for given impress.js root element\n        return ( roots[ \"impress-root-\" + rootId ] = {\n            init: init,\n            goto: goto,\n            next: next,\n            prev: prev,\n            swipe: swipe,\n            tear: tear,\n            lib: lib\n        } );\n\n    };\n\n    // Flag that can be used in JS to check if browser have passed the support test\n    impress.supported = impressSupported;\n\n    // ADD and INIT LIBRARIES\n    // Library factories are defined in src/lib/*.js, and register themselves by calling\n    // impress.addLibraryFactory(libraryFactoryObject). They're stored here, and used to augment\n    // the API with library functions when client calls impress(rootId).\n    // See src/lib/README.md for clearer example.\n    // (Advanced usage: For different values of rootId, a different instance of the libaries are\n    // generated, in case they need to hold different state for different root elements.)\n    var libraryFactories = {};\n    impress.addLibraryFactory = function( obj ) {\n        for ( var libname in obj ) {\n            if ( obj.hasOwnProperty( libname ) ) {\n                libraryFactories[ libname ] = obj[ libname ];\n            }\n        }\n    };\n\n    // Call each library factory, and return the lib object that is added to the api.\n    var initLibraries = function( rootId ) { //jshint ignore:line\n        var lib = {};\n        for ( var libname in libraryFactories ) {\n            if ( libraryFactories.hasOwnProperty( libname ) ) {\n                if ( lib[ libname ] !== undefined ) {\n                    throw \"impress.js ERROR: Two libraries both tried to use libname: \" +  libname;\n                }\n                lib[ libname ] = libraryFactories[ libname ]( rootId );\n            }\n        }\n        return lib;\n    };\n\n    // `addPreInitPlugin` allows plugins to register a function that should\n    // be run (synchronously) at the beginning of init, before\n    // impress().init() itself executes.\n    impress.addPreInitPlugin = function( plugin, weight ) {\n        weight = parseInt( weight ) || 10;\n        if ( weight <= 0 ) {\n            throw \"addPreInitPlugin: weight must be a positive integer\";\n        }\n\n        if ( preInitPlugins[ weight ] === undefined ) {\n            preInitPlugins[ weight ] = [];\n        }\n        preInitPlugins[ weight ].push( plugin );\n    };\n\n    // Called at beginning of init, to execute all pre-init plugins.\n    var execPreInitPlugins = function( root ) { //jshint ignore:line\n        for ( var i = 0; i < preInitPlugins.length; i++ ) {\n            var thisLevel = preInitPlugins[ i ];\n            if ( thisLevel !== undefined ) {\n                for ( var j = 0; j < thisLevel.length; j++ ) {\n                    thisLevel[ j ]( root, roots[ \"impress-root-\" + root.id ] );\n                }\n            }\n        }\n    };\n\n    // `addPreStepLeavePlugin` allows plugins to register a function that should\n    // be run (synchronously) at the beginning of goto()\n    impress.addPreStepLeavePlugin = function( plugin, weight ) { //jshint ignore:line\n        weight = parseInt( weight ) || 10;\n        if ( weight <= 0 ) {\n            throw \"addPreStepLeavePlugin: weight must be a positive integer\";\n        }\n\n        if ( preStepLeavePlugins[ weight ] === undefined ) {\n            preStepLeavePlugins[ weight ] = [];\n        }\n        preStepLeavePlugins[ weight ].push( plugin );\n    };\n\n    impress.getConfig = function() {\n        return config;\n    };\n\n    // Called at beginning of goto(), to execute all preStepLeave plugins.\n    var execPreStepLeavePlugins = function( event ) { //jshint ignore:line\n        for ( var i = 0; i < preStepLeavePlugins.length; i++ ) {\n            var thisLevel = preStepLeavePlugins[ i ];\n            if ( thisLevel !== undefined ) {\n                for ( var j = 0; j < thisLevel.length; j++ ) {\n                    if ( thisLevel[ j ]( event ) === false ) {\n\n                        // If a plugin returns false, the stepleave event (and related transition)\n                        // is aborted\n                        return false;\n                    }\n                }\n            }\n        }\n    };\n\n} )( document, window );\n\n// THAT'S ALL FOLKS!\n//\n// Thanks for reading it all.\n// Or thanks for scrolling down and reading the last part.\n//\n// I've learnt a lot when building impress.js and I hope this code and comments\n// will help somebody learn at least some part of it.\n"
  },
  {
    "path": "src/lib/README.md",
    "content": "Impress.js Libraries\n====================\n\nThe `src/lib/*.js` files contain library functions. The main difference to plugins is that:\n\n1. Libraries are closer to the impress.js core than plugins (arguably a subjective metric)\n2. Libraries are common utility functions used by many plugins\n3. Libraries are called synchronously, which is why the event based paradigm that plugins use to\n   communicate isn't useful.\n\nPlugins can access libraries via the API:\n\n    var api;\n    document.addEventListener( \"impress:init\", function(event){\n        api = event.detail.api;\n        api().lib.<libraryName>.<libaryFunction>();\n    });\n\n...which is equivalent to:\n\n    impress().lib.<libraryName>.<libraryFunction>();\n\nImplementing a library\n----------------------\n\n1. Create a file under `src/lib/`.\n\n2. Start with the standard boilerplate documentation, and the (function(document, window){})(); \nwrapper.\n\n3. The library should implement a factory function, and make its existence known to impress.js core:\n\n    window.impress.addLibraryFactory( { libName : libraryFactory} );\n\n4. The library function should return a similar API object as core `impress()` function does:\n\n    var libraryFactory = function(rootId) {\n        /* implement library functions ... */\n        \n        var lib = {\n            libFunction1: libFunction1,\n            libFunction2: libFunction2\n        }\n        return lib;\n    };\n\n5. While rarely used, impress.js actually supports multiple presentation root div elements on a\nsingle html page. Each of these have their own API object, identified by the root element id\nattribute:\n\n    impress(\"other-root-id\").init();\n\n(The default rootId obviously is `\"impress\"`.)\n\nLibraries MUST implement this support for multiple root elements as well. \n\n- impress.js core will call the factory once for each separate root element being initialized via\n  `impress.init(rootId)`.\n- Any state that a library might hold, MUST be stored *per `rootId`*.\n- Note that as we now support also `impress(rootId).tear()`, the same root element might be\n  initialized more than once, and each of these MUST be treated as a new valid initialization.\n\nPutting all of the above together, a skeleton library file will look like:\n\n    /**\n    * Example library libName\n    *\n    * Henrik Ingo (c) 2016\n    * MIT License\n    */\n    (function ( document, window ) {\n        'use strict';\n        // Singleton library variables\n        var roots = [];\n        var singletonVar = {};\n        \n        var libraryFactory = function(rootId) {\n            if (roots[\"impress-root-\" + rootId]) {\n                return roots[\"impress-root-\" + rootId];\n            }\n            \n            // Per root global variables (instance variables?)\n            var instanceVar = {};\n\n            // LIBRARY FUNCTIONS\n            var libraryFunction1 = function () {\n                /* ... */\n            };\n            \n            var libraryFunction2 = function () {\n                /* ... */\n            };\n            \n            var lib = {\n                    libFunction1: libFunction1,\n                    libFunction2: libFunction2\n                }\n            roots[\"impress-root-\" + rootId] = lib;\n            return lib;\n        };\n        \n        // Let impress core know about the existence of this library\n        window.impress.addLibraryFactory( { libName : libraryFactory } );\n        \n    })(document, window);\n"
  },
  {
    "path": "src/lib/gc.js",
    "content": "/**\n * Garbage collection utility\n *\n * This library allows plugins to add elements and event listeners they add to the DOM. The user\n * can call `impress().lib.gc.teardown()` to cause all of them to be removed from DOM, so that\n * the document is in the state it was before calling `impress().init()`.\n *\n * In addition to just adding elements and event listeners to the garbage collector, plugins\n * can also register callback functions to do arbitrary cleanup upon teardown.\n *\n * Henrik Ingo (c) 2016\n * MIT License\n */\n\n( function( document, window ) {\n    \"use strict\";\n    var roots = [];\n    var rootsCount = 0;\n    var startingState = { roots: [] };\n\n    var libraryFactory = function( rootId ) {\n        if ( roots[ rootId ] ) {\n            return roots[ rootId ];\n        }\n\n        // Per root global variables (instance variables?)\n        var elementList = [];\n        var eventListenerList = [];\n        var callbackList = [];\n\n        recordStartingState( rootId );\n\n        // LIBRARY FUNCTIONS\n        // Definitions of the library functions we return as an object at the end\n\n        // `pushElement` adds a DOM element to the gc stack\n        var pushElement = function( element ) {\n            elementList.push( element );\n        };\n\n        // `appendChild` is a convenience wrapper that combines DOM appendChild with gc.pushElement\n        var appendChild = function( parent, element ) {\n            parent.appendChild( element );\n            pushElement( element );\n        };\n\n        // `pushEventListener` adds an event listener to the gc stack\n        var pushEventListener = function( target, type, listenerFunction ) {\n            eventListenerList.push( { target:target, type:type, listener:listenerFunction } );\n        };\n\n        // `addEventListener` combines DOM addEventListener with gc.pushEventListener\n        var addEventListener = function( target, type, listenerFunction ) {\n            target.addEventListener( type, listenerFunction );\n            pushEventListener( target, type, listenerFunction );\n        };\n\n        // `pushCallback` If the above utilities are not enough, plugins can add their own callback\n        // function to do arbitrary things.\n        var pushCallback = function( callback ) {\n            callbackList.push( callback );\n        };\n        pushCallback( function( rootId ) { resetStartingState( rootId ); } );\n\n        // `teardown` will\n        // - execute all callbacks in LIFO order\n        // - call `removeChild` on all DOM elements in LIFO order\n        // - call `removeEventListener` on all event listeners in LIFO order\n        // The goal of a teardown is to return to the same state that the DOM was before\n        // `impress().init()` was called.\n        var teardown = function() {\n\n            // Execute the callbacks in LIFO order\n            var i; // Needed by jshint\n            for ( i = callbackList.length - 1; i >= 0; i-- ) {\n                callbackList[ i ]( rootId );\n            }\n            callbackList = [];\n            for ( i = 0; i < elementList.length; i++ ) {\n                elementList[ i ].parentElement.removeChild( elementList[ i ] );\n            }\n            elementList = [];\n            for ( i = 0; i < eventListenerList.length; i++ ) {\n                var target   = eventListenerList[ i ].target;\n                var type     = eventListenerList[ i ].type;\n                var listener = eventListenerList[ i ].listener;\n                target.removeEventListener( type, listener );\n            }\n        };\n\n        var lib = {\n            pushElement: pushElement,\n            appendChild: appendChild,\n            pushEventListener: pushEventListener,\n            addEventListener: addEventListener,\n            pushCallback: pushCallback,\n            teardown: teardown\n        };\n        roots[ rootId ] = lib;\n        rootsCount++;\n        return lib;\n    };\n\n    // Let impress core know about the existence of this library\n    window.impress.addLibraryFactory( { gc: libraryFactory } );\n\n    // CORE INIT\n    // The library factory (gc(rootId)) is called at the beginning of impress(rootId).init()\n    // For the purposes of teardown(), we can use this as an opportunity to save the state\n    // of a few things in the DOM in their virgin state, before impress().init() did anything.\n    // Note: These could also be recorded by the code in impress.js core as these values\n    // are changed, but in an effort to not deviate too much from upstream, I'm adding\n    // them here rather than the core itself.\n    var recordStartingState = function( rootId ) {\n        startingState.roots[ rootId ] = {};\n        startingState.roots[ rootId ].steps = [];\n\n        // Record whether the steps have an id or not\n        var steps = document.getElementById( rootId ).querySelectorAll( \".step\" );\n        for ( var i = 0; i < steps.length; i++ ) {\n            var el = steps[ i ];\n            startingState.roots[ rootId ].steps.push( {\n                el: el,\n                id: el.getAttribute( \"id\" )\n            } );\n        }\n\n        // In the rare case of multiple roots, the following is changed on first init() and\n        // reset at last tear().\n        if ( rootsCount === 0 ) {\n            startingState.body = {};\n\n            // It is customary for authors to set body.class=\"impress-not-supported\" as a starting\n            // value, which can then be removed by impress().init(). But it is not required.\n            // Remember whether it was there or not.\n            if ( document.body.classList.contains( \"impress-not-supported\" ) ) {\n                startingState.body.impressNotSupported = true;\n            } else {\n                startingState.body.impressNotSupported = false;\n            }\n\n            // If there's a <meta name=\"viewport\"> element, its contents will be overwritten by init\n            var metas = document.head.querySelectorAll( \"meta\" );\n            for ( i = 0; i < metas.length; i++ ) {\n                var m = metas[ i ];\n                if ( m.name === \"viewport\" ) {\n                    startingState.meta = m.content;\n                }\n            }\n        }\n    };\n\n    // CORE TEARDOWN\n    var resetStartingState = function( rootId ) {\n\n        // Reset body element\n        document.body.classList.remove( \"impress-enabled\" );\n        document.body.classList.remove( \"impress-disabled\" );\n\n        var root = document.getElementById( rootId );\n        var activeId = root.querySelector( \".active\" ).id;\n        document.body.classList.remove( \"impress-on-\" + activeId );\n\n        document.documentElement.style.height = \"\";\n        document.body.style.height = \"\";\n        document.body.style.overflow = \"\";\n\n        // Remove style values from the root and step elements\n        // Note: We remove the ones set by impress.js core. Otoh, we didn't preserve any original\n        // values. A more sophisticated implementation could keep track of original values and then\n        // reset those.\n        var steps = root.querySelectorAll( \".step\" );\n        for ( var i = 0; i < steps.length; i++ ) {\n            steps[ i ].classList.remove( \"future\" );\n            steps[ i ].classList.remove( \"past\" );\n            steps[ i ].classList.remove( \"present\" );\n            steps[ i ].classList.remove( \"active\" );\n            steps[ i ].style.position = \"\";\n            steps[ i ].style.transform = \"\";\n            steps[ i ].style[ \"transform-style\" ] = \"\";\n        }\n        root.style.position = \"\";\n        root.style[ \"transform-origin\" ] = \"\";\n        root.style.transition = \"\";\n        root.style[ \"transform-style\" ] = \"\";\n        root.style.top = \"\";\n        root.style.left = \"\";\n        root.style.transform = \"\";\n\n        // Reset id of steps (\"step-1\" id's are auto generated)\n        steps = startingState.roots[ rootId ].steps;\n        var step;\n        while ( step = steps.pop() ) {\n            if ( step.id === null ) {\n                step.el.removeAttribute( \"id\" );\n            } else {\n                step.el.setAttribute( \"id\", step.id );\n            }\n        }\n        delete startingState.roots[ rootId ];\n\n        // Move step div elements away from canvas, then delete canvas\n        // Note: There's an implicit assumption here that the canvas div is the only child element\n        // of the root div. If there would be something else, it's gonna be lost.\n        var canvas = root.firstChild;\n        var canvasHTML = canvas.innerHTML;\n        root.innerHTML = canvasHTML;\n\n        if ( roots[ rootId ] !== undefined ) {\n            delete roots[ rootId ];\n            rootsCount--;\n        }\n        if ( rootsCount === 0 ) {\n\n            // In the rare case that more than one impress root elements were initialized, these\n            // are only reset when all are uninitialized.\n            document.body.classList.remove( \"impress-supported\" );\n            if ( startingState.body.impressNotSupported ) {\n                document.body.classList.add( \"impress-not-supported\" );\n            }\n\n            // We need to remove or reset the meta element inserted by impress.js\n            var metas = document.head.querySelectorAll( \"meta\" );\n            for ( i = 0; i < metas.length; i++ ) {\n                var m = metas[ i ];\n                if ( m.name === \"viewport\" ) {\n                    if ( startingState.meta !== undefined ) {\n                        m.content = startingState.meta;\n                    } else {\n                        m.parentElement.removeChild( m );\n                    }\n                }\n            }\n        }\n\n    };\n\n} )( document, window );\n"
  },
  {
    "path": "src/lib/rotation.js",
    "content": "/**\n * Helper functions for rotation.\n *\n * Tommy Tam (c) 2021\n * MIT License\n */\n( function( document, window ) {\n    \"use strict\";\n\n    // Singleton library variables\n    var roots = [];\n\n    var libraryFactory = function( rootId ) {\n        if ( roots[ \"impress-root-\" + rootId ] ) {\n            return roots[ \"impress-root-\" + rootId ];\n        }\n\n        /**\n         * Round the number to 2 decimals, it's enough for use\n         */\n        var roundNumber = function( num ) {\n            return Math.round( ( num + Number.EPSILON ) * 100 ) / 100;\n        };\n\n        /**\n         * Get the length/norm of a vector.\n         *\n         * https://en.wikipedia.org/wiki/Norm_(mathematics)\n         */\n        var vectorLength = function( vec ) {\n            return Math.sqrt( vec.x * vec.x + vec.y * vec.y + vec.z * vec.z );\n        };\n\n        /**\n         * Dot product of two vectors.\n         *\n         * https://en.wikipedia.org/wiki/Dot_product\n         */\n        var vectorDotProd = function( vec1, vec2 ) {\n            return vec1.x * vec2.x + vec1.y * vec2.y + vec1.z * vec2.z;\n        };\n\n        /**\n         * Cross product of two vectors.\n         *\n         * https://en.wikipedia.org/wiki/Cross_product\n         */\n        var vectorCrossProd = function( vec1, vec2 ) {\n            return {\n                x: vec1.y * vec2.z - vec1.z * vec2.y,\n                y: vec1.z * vec2.x - vec1.x * vec2.z,\n                z: vec1.x * vec2.y - vec1.y * vec2.x\n            };\n        };\n\n        /**\n         * Determine wheter a vector is a zero vector\n         */\n        var isZeroVector = function( vec ) {\n            return !roundNumber( vec.x ) && !roundNumber( vec.y ) && !roundNumber( vec.z );\n        };\n\n        /**\n         * Scalar triple product of three vectors.\n         *\n         * It can be used to determine the handness of vectors.\n         *\n         * https://en.wikipedia.org/wiki/Triple_product#Scalar_triple_product\n         */\n        var tripleProduct = function( vec1, vec2, vec3 ) {\n            return vectorDotProd( vectorCrossProd( vec1, vec2 ), vec3 );\n        };\n\n        /**\n         * The world/absolute unit coordinates.\n         *\n         * This coordinate is used by browser to position objects.\n         * It will not be affected by object rotations.\n         * All relative positions will finally be converted to this\n         * coordinate to be used.\n         */\n        var worldUnitCoordinate = {\n            x: { x:1, y:0, z:0 },\n            y: { x:0, y:1, z:0 },\n            z: { x:0, y:0, z:1 }\n        };\n\n        /**\n         * Make quaternion from rotation axis and angle.\n         *\n         * q = [ cos(½θ), sin(½θ) axis ]\n         *\n         * If the angle is zero, returns the corresponded quaternion\n         * of axis.\n         *\n         * If the angle is not zero, returns the rotating quaternion\n         * which corresponds to rotation about the axis, by the angle θ.\n         *\n         * https://en.wikipedia.org/wiki/Quaternion\n         * https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation\n         */\n        var makeQuaternion = function( axis, theta = 0 ) {\n            var r = 0;\n            var t = 1;\n\n            if ( theta ) {\n                var radians = theta * Math.PI / 180;\n                r = Math.cos( radians / 2 );\n                t = Math.sin( radians / 2 ) / vectorLength( axis );\n            }\n\n            var q = [ r, axis.x * t, axis.y * t, axis.z * t ];\n\n            return q;\n        };\n\n        /**\n         * Extract vector from quaternion\n         */\n        var quaternionToVector = function( quaternion ) {\n            return {\n                x: roundNumber( quaternion[ 1 ] ),\n                y: roundNumber( quaternion[ 2 ] ),\n                z: roundNumber( quaternion[ 3 ] )\n            };\n        };\n\n        /**\n         * Returns the conjugate quaternion of a quaternion\n         *\n         * https://en.wikipedia.org/wiki/Quaternion#Conjugation,_the_norm,_and_reciprocal\n         */\n        var conjugateQuaternion = function( quaternion ) {\n            return [ quaternion[ 0 ], -quaternion[ 1 ], -quaternion[ 2 ], -quaternion[ 3 ] ];\n        };\n\n        /**\n         * Left multiple two quaternion.\n         *\n         * Is's used to combine two rotating quaternion into one.\n         */\n        var leftMulQuaternion = function( q1, q2 ) {\n            return [\n                ( q1[ 0 ] * q2[ 0 ] - q1[ 1 ] * q2[ 1 ] - q1[ 2 ] * q2[ 2 ] - q1[ 3 ] * q2[ 3 ] ),\n                ( q1[ 1 ] * q2[ 0 ] + q1[ 0 ] * q2[ 1 ] - q1[ 3 ] * q2[ 2 ] + q1[ 2 ] * q2[ 3 ] ),\n                ( q1[ 2 ] * q2[ 0 ] + q1[ 3 ] * q2[ 1 ] + q1[ 0 ] * q2[ 2 ] - q1[ 1 ] * q2[ 3 ] ),\n                ( q1[ 3 ] * q2[ 0 ] - q1[ 2 ] * q2[ 1 ] + q1[ 1 ] * q2[ 2 ] + q1[ 0 ] * q2[ 3 ] )\n            ];\n        };\n\n        /**\n         * Convert a rotation into a quaternion\n         */\n        var rotationToQuaternion = function( baseCoordinate, rotation ) {\n            var order = rotation.order ? rotation.order : \"xyz\";\n            var axes = order.split( \"\" );\n            var result = [ 1, 0, 0, 0 ];\n\n            for ( var i = 0; i < axes.length; i++ ) {\n                var deg = rotation[ axes[ i ] ];\n                if ( !deg || ( Math.abs( deg ) < 0.0001 ) ) {\n                    continue;\n                }\n\n                // All CSS rotation is based on the rotated coordinate\n                // So we need to calculate the rotated coordinate first\n                var coordinate = baseCoordinate;\n                if ( i > 0 ) {\n                    coordinate = {\n                        x: rotateByQuaternion( baseCoordinate.x, result ),\n                        y: rotateByQuaternion( baseCoordinate.y, result ),\n                        z: rotateByQuaternion( baseCoordinate.z, result )\n                    };\n                }\n\n                result = leftMulQuaternion(\n                    makeQuaternion( coordinate[ axes[ i ] ], deg ),\n                    result );\n\n            }\n\n            return result;\n        };\n\n        /**\n         * Rotate a vector by a quaternion.\n         */\n        var rotateByQuaternion = function( vec, quaternion ) {\n            var q = makeQuaternion( vec );\n\n            q = leftMulQuaternion(\n                leftMulQuaternion( quaternion, q ),\n                conjugateQuaternion( quaternion ) );\n\n            return quaternionToVector( q );\n        };\n\n        /**\n         * Rotate a vector by rotaion sequence.\n         */\n        var rotateVector = function( baseCoordinate, vec, rotation ) {\n            var quaternion = rotationToQuaternion( baseCoordinate, rotation );\n\n            return rotateByQuaternion( vec, quaternion );\n        };\n\n        /**\n         * Given a rotation, return the rotationed coordinate\n         */\n        var rotateCoordinate = function( coordinate, rotation ) {\n            var quaternion = rotationToQuaternion( coordinate, rotation );\n\n            return {\n                x: rotateByQuaternion( coordinate.x, quaternion ),\n                y: rotateByQuaternion( coordinate.y, quaternion ),\n                z: rotateByQuaternion( coordinate.z, quaternion )\n            };\n        };\n\n        /**\n         * Return the angle between two vector.\n         *\n         * The axis is used to determine the rotation direction.\n         */\n        var angleBetweenTwoVector = function( axis, vec1, vec2 ) {\n            var vecLen1 = vectorLength( vec1 );\n            var vecLen2 = vectorLength( vec2 );\n\n            if ( !vecLen1 || !vecLen2 ) {\n                return 0;\n            }\n\n            var cos = vectorDotProd( vec1, vec2 ) / vecLen1 / vecLen2 ;\n            var angle = Math.acos( cos ) * 180 / Math.PI;\n\n            if ( tripleProduct( vec1, vec2, axis ) > 0 ) {\n                return angle;\n            } else {\n                return -angle;\n            }\n        };\n\n        /**\n         * Return the angle between a vector and a plane.\n         *\n         * The plane is determined by an axis and a vector on the plane.\n         */\n        var angleBetweenPlaneAndVector = function( axis, planeVec, rotatedVec ) {\n            var norm = vectorCrossProd( axis, planeVec );\n\n            if ( isZeroVector( norm ) ) {\n                return 0;\n            }\n\n            return 90 - angleBetweenTwoVector( axis, rotatedVec, norm );\n        };\n\n        /**\n         * Calculated a order specified rotation sequence to\n         * transform from the world coordinate to required coordinate.\n         */\n        var coordinateToOrderedRotation = function( coordinate, order ) {\n            var axis0 = order[ 0 ];\n            var axis1 = order[ 1 ];\n            var axis2 = order[ 2 ];\n            var reversedOrder = order.split( \"\" ).reverse().join( \"\" );\n\n            var rotate2 = angleBetweenPlaneAndVector(\n                coordinate[ axis2 ],\n                worldUnitCoordinate[ axis0 ],\n                coordinate[ axis0 ] );\n\n            // The r2 is the reverse of rotate for axis2\n            // The coordinate1 is the coordinate before rotate of axis2\n            var r2 = { order: reversedOrder };\n            r2[ axis2 ] = -rotate2;\n\n            var coordinate1 = rotateCoordinate( coordinate, r2 );\n\n            // Calculate the rotation for axis1\n            var rotate1 = angleBetweenTwoVector(\n                coordinate1[ axis1 ],\n                worldUnitCoordinate[ axis0 ],\n                coordinate1[ axis0 ] );\n\n            // Calculate the rotation for axis0\n            var rotate0 = angleBetweenTwoVector(\n                worldUnitCoordinate[ axis0 ],\n                worldUnitCoordinate[ axis1 ],\n                coordinate1[ axis1 ] );\n\n            var rotation = { };\n            rotation.order = order;\n            rotation[ axis0 ] = roundNumber( rotate0 );\n            rotation[ axis1 ] = roundNumber( rotate1 );\n            rotation[ axis2 ] = roundNumber( rotate2 );\n\n            return rotation;\n        };\n\n        /**\n         * Returns the possible rotations from unit coordinate\n         * to specified coordinate.\n         */\n        var possibleRotations = function( coordinate ) {\n            var orders = [ \"xyz\", \"xzy\", \"yxz\", \"yzx\", \"zxy\", \"zyx\" ];\n            var rotations = [ ];\n\n            for ( var i = 0; i < orders.length; ++i ) {\n                rotations.push(\n                    coordinateToOrderedRotation( coordinate, orders[ i ] )\n                );\n            }\n\n            return rotations;\n        };\n\n        /**\n         * Calculate a degree which in range (-180, 180] of baseDeg\n         */\n        var nearestAngle = function( baseDeg, deg ) {\n            while ( deg > baseDeg + 180 ) {\n                deg -= 360;\n            }\n\n            while ( deg < baseDeg - 180 ) {\n                deg += 360;\n            }\n\n            return deg;\n        };\n\n        /**\n         * Given a base rotation and multiple rotations, return the best one.\n         *\n         * The best one is the one has least rotate from base.\n         */\n        var bestRotation = function( baseRotate, rotations ) {\n            var bestScore;\n            var bestRotation;\n\n            for ( var i = 0; i < rotations.length; ++i ) {\n                var rotation = {\n                    order: rotations[ i ].order,\n                    x: nearestAngle( baseRotate.x, rotations[ i ].x ),\n                    y: nearestAngle( baseRotate.y, rotations[ i ].y ),\n                    z: nearestAngle( baseRotate.z, rotations[ i ].z )\n                };\n\n                var score = Math.abs( rotation.x - baseRotate.x ) +\n                    Math.abs( rotation.y - baseRotate.y ) +\n                    Math.abs( rotation.z - baseRotate.z );\n\n                if ( !i || ( score < bestScore ) ) {\n                    bestScore = score;\n                    bestRotation = rotation;\n                }\n            }\n\n            return bestRotation;\n        };\n\n        /**\n         * Given a coordinate, return the best rotation to achieve it.\n         *\n         * The baseRotate is used to select the near rotation from it.\n         */\n        var coordinateToRotation = function( baseRotate, coordinate ) {\n            var rotations = possibleRotations( coordinate );\n\n            return bestRotation( baseRotate, rotations );\n        };\n\n        /**\n         * Apply a relative rotation to the base rotation.\n         *\n         * Calculate the coordinate after the rotation on each axis,\n         * and finally find out a one step rotation has the effect\n         * of two rotation.\n         *\n         * If there're multiple way to accomplish, select the one\n         * that is nearest to the base.\n         *\n         * Return one rotation has the same effect.\n         */\n        var combineRotations = function( rotations ) {\n\n            // No rotation\n            if ( rotations.length <= 0 ) {\n                return { x:0, y:0, z:0, order:\"xyz\" };\n            }\n\n            // Find out the base coordinate\n            var coordinate = worldUnitCoordinate;\n\n            // One by one apply rotations in order\n            for ( var i = 0; i < rotations.length; i++ ) {\n                coordinate = rotateCoordinate( coordinate, rotations[ i ] );\n            }\n\n            // Calculate one rotation from unit coordinate to rotated\n            // coordinate.  Because there're multiple possibles,\n            // select the one nearest to the base\n            var rotate = coordinateToRotation( rotations[ 0 ], coordinate );\n\n            return rotate;\n        };\n\n        var translateRelative = function( relative, prevRotation ) {\n            var result = rotateVector(\n                worldUnitCoordinate, relative, prevRotation );\n            result.rotate = combineRotations(\n                [ prevRotation, relative.rotate ] );\n\n            return result;\n        };\n\n        var lib = {\n            translateRelative: translateRelative\n        };\n\n        roots[ \"impress-root-\" + rootId ] = lib;\n        return lib;\n    };\n\n    // Let impress core know about the existence of this library\n    window.impress.addLibraryFactory( { rotation: libraryFactory } );\n\n} )( document, window );\n"
  },
  {
    "path": "src/lib/util.js",
    "content": "/**\n * Common utility functions\n *\n * Copyright 2011-2012 Bartek Szopka (@bartaz)\n * Henrik Ingo (c) 2016\n * MIT License\n */\n\n( function( document, window ) {\n    \"use strict\";\n    var roots = [];\n\n    var libraryFactory = function( rootId ) {\n        if ( roots[ rootId ] ) {\n            return roots[ rootId ];\n        }\n\n        // `$` returns first element for given CSS `selector` in the `context` of\n        // the given element or whole document.\n        var $ = function( selector, context ) {\n            context = context || document;\n            return context.querySelector( selector );\n        };\n\n        // `$$` return an array of elements for given CSS `selector` in the `context` of\n        // the given element or whole document.\n        var $$ = function( selector, context ) {\n            context = context || document;\n            return arrayify( context.querySelectorAll( selector ) );\n        };\n\n        // `arrayify` takes an array-like object and turns it into real Array\n        // to make all the Array.prototype goodness available.\n        var arrayify = function( a ) {\n            return [].slice.call( a );\n        };\n\n        // `byId` returns element with given `id` - you probably have guessed that ;)\n        var byId = function( id ) {\n            return document.getElementById( id );\n        };\n\n        // `getElementFromHash` returns an element located by id from hash part of\n        // window location.\n        var getElementFromHash = function() {\n\n            // Get id from url # by removing `#` or `#/` from the beginning,\n            // so both \"fallback\" `#slide-id` and \"enhanced\" `#/slide-id` will work\n            var encoded = window.location.hash.replace( /^#\\/?/, \"\" );\n            return byId( decodeURIComponent( encoded ) );\n        };\n\n        // `getUrlParamValue` return a given URL parameter value if it exists\n        // `undefined` if it doesn't exist\n        var getUrlParamValue = function( parameter ) {\n            var chunk = window.location.search.split( parameter + \"=\" )[ 1 ];\n            var value = chunk && chunk.split( \"&\" )[ 0 ];\n\n            if ( value !== \"\" ) {\n                return value;\n            }\n        };\n\n        // Throttling function calls, by Remy Sharp\n        // http://remysharp.com/2010/07/21/throttling-function-calls/\n        var throttle = function( fn, delay ) {\n            var timer = null;\n            return function() {\n                var context = this, args = arguments;\n                window.clearTimeout( timer );\n                timer = window.setTimeout( function() {\n                    fn.apply( context, args );\n                }, delay );\n            };\n        };\n\n        // `toNumber` takes a value given as `numeric` parameter and tries to turn\n        // it into a number. If it is not possible it returns 0 (or other value\n        // given as `fallback`).\n        var toNumber = function( numeric, fallback ) {\n            return isNaN( numeric ) ? ( fallback || 0 ) : Number( numeric );\n        };\n\n        /**\n         * Extends toNumber() to correctly compute also relative-to-screen-size values 5w and 5h.\n         *\n         * Returns the computed value in pixels with w/h postfix removed.\n         */\n        var toNumberAdvanced = function( numeric, fallback ) {\n            if ( typeof numeric !== \"string\" ) {\n                return toNumber( numeric, fallback );\n            }\n            var ratio = numeric.match( /^([+-]*[\\d\\.]+)([wh])$/ );\n            if ( ratio == null ) {\n                return toNumber( numeric, fallback );\n            } else {\n                var value = parseFloat( ratio[ 1 ] );\n                var config = window.impress.getConfig();\n                var multiplier = ratio[ 2 ] === \"w\" ? config.width : config.height;\n                return value * multiplier;\n            }\n        };\n\n        // `triggerEvent` builds a custom DOM event with given `eventName` and `detail` data\n        // and triggers it on element given as `el`.\n        var triggerEvent = function( el, eventName, detail ) {\n            var event = document.createEvent( \"CustomEvent\" );\n            event.initCustomEvent( eventName, true, true, detail );\n            el.dispatchEvent( event );\n        };\n\n        var lib = {\n            $: $,\n            $$: $$,\n            arrayify: arrayify,\n            byId: byId,\n            getElementFromHash: getElementFromHash,\n            throttle: throttle,\n            toNumber: toNumber,\n            toNumberAdvanced: toNumberAdvanced,\n            triggerEvent: triggerEvent,\n            getUrlParamValue: getUrlParamValue\n        };\n        roots[ rootId ] = lib;\n        return lib;\n    };\n\n    // Let impress core know about the existence of this library\n    window.impress.addLibraryFactory( { util: libraryFactory } );\n\n} )( document, window );\n"
  },
  {
    "path": "src/plugins/README.md",
    "content": "Impress.js Plugins documentation\n================================\n\nThe default set of plugins\n--------------------------\n\nA lot of impress.js features are and will be implemented as plugins. Each plugin\nhas user documentation in a README.md file in [its own directory](./).\n\nThe plugins in this directory are called default plugins, and - unsurprisingly -\nare enabled by default. However, most of them won't do anything by default, \nrather require the user to invoke them somehow. For example:\n\n* The *navigation* plugin waits for the user to press some keys, arrows, page\n  down, page up, space or tab.\n* The *autoplay* plugin looks for the HTML attribute `data-autoplay` to see\n  whether it should do its thing. It can also be triggered with a URL GET parameter\n  `?impress-autoplay=5` *5 is the waiting duration*. \n* The *toolbar* plugin looks for a `<div>` element to become visible.\n\nExtra addons\n------------\n\nYet more features are available in presentations that enable \n[extra addons](https://github.com/impress/impress-extras). Extra addons are 3rd party plugins \nthat are not part of impress.js, but that we have nevertheless collected together into the \nimpress-extras repo to provide convenient and standardized access to them. To include \nthe extra addons when checking out impress.js, use git clone --recursive. Even then, they \nare not activated by default in a presentation, rather each must be included with their own `<script>` tag.\n\nNote: The enabled extra addons are automatically initialized by the *extras*\nplugin.\n\nExample HTML and CSS\n--------------------\n\nGenerally plugins will do something sane, or nothing, by default. Hence, no\nparticular HTML or CSS is required. The README file of each plugin documents the\nHTML and CSS that you can use with that plugin.\n\nFor your convenience, below is some sample HTML and CSS code covering all the\nplugins that you may want to use or adapt.\n\nAdditional parameters for addons\n--------------------------------\n\nSome addons can handle additional HTML data attributes to help us in further customization:\n- Markdown-JS: You can pass a specific Markdown dialect to the plugin using `data-markdown-dialect=\"Another Dialect\"`.\n\n### Sample HTML to enable plugins and extra addons\n\n    <head>\n      <!-- CSS files if using Highlight.js or Mermaid.js extras. -->\n      <link rel=\"stylesheet\" href=\"../../extras/highlight/styles/github.css\">\n      <link rel=\"stylesheet\" href=\"../../extras/mermaid/mermaid.forest.css\">\n    </head>\n    <body>\n      <div id=\"impress\" data-autoplay=\"10\">\n        <div class=\"step\"\n             data-autoplay=\"15\"\n             data-rel-x=\"1000\"\n             data-rel-y=\"1000\">\n\n          <h1>Slide content</h1>\n          \n          <ul>\n            <li class=\"substep\">Point 1</li>\n            <li class=\"substep\">Point 2</li>\n          </ul>\n\n          <div class=\"notes\">\n          Speaker notes are shown in the impressConsole.\n          </div>\n        </div>\n      </div>\n      \n      <div id=\"impress-toolbar\"></div>\n      <div class=\"impress-progressbar\"><div></div></div>\n      <div class=\"impress-progress\"></div>\n      <div id=\"impress-help\"></div>\n\n      <script type=\"text/javascript\" src=\"../../extras/highlight/highlight.pack.js\"></script>\n      <script type=\"text/javascript\" src=\"../../extras/mermaid/mermaid.min.js\"></script>\n      <script type=\"text/javascript\" src=\"../../extras/markdown/markdown.js\"></script>\n      <script type=\"text/javascript\" src=\"../../extras/mathjax/MathJax.js?config=TeX-AMS_CHTML\"></script>\n    </body>\n\n### Sample CSS related to plugins and extra addons\n\nThe sample css related to plugins and extra addons is located in [css/impress-common.css](../../css/impress-common.css).\n\nFor developers\n==============\n\nThe vision for impress.js is to provide a compact core library doing the\nactual presentations, with a collection of plugins that provide additional\nfunctionality. A default set of plugins are distributed together with the core \nimpress.js, and are located in this directory. They are called *default plugins*\nbecause they are distributed and active when users use the [js/impress.js](../../js/impress.js)\nin their presentations.\n\nBuilding js/impress.js\n-----------------------\n\nThe common way to use impress.js is to link to the file \n[js/impress.js](../../js/impress.js). This is a simple concatenation of the \ncore impress.js and all plugins in this directory. If you edit or add code \nunder [src/](../), you can run `node build.js` to recreate the distributable\n`js/impress.js` file. The build script also creates a minified file, but this\nis not included in the git repository.\n\n### Tip: Build errors\n\nIf your code has parse errors, the `build.js` will print a rather unhelpful\nexception like\n\n    /home/hingo/hacking/impress.js/js/impress.js\n\n    /home/hingo/hacking/impress.js/node_modules/uglify-js/lib/parse-js.js:271\n        throw new JS_Parse_Error(message, line, col, pos);\n              ^\n    Error\n        at new JS_Parse_Error (/home/hingo/hacking/impress.js/node_modules/uglify-js/lib/parse-js.js:263:18)\n        at js_error (/home/hingo/hacking/impress.js/node_modules/uglify-js/lib/parse-js.js:271:11)\n        at croak (/home/hingo/hacking/impress.js/node_modules/uglify-js/lib/parse-js.js:733:9)\n        at token_error (/home/hingo/hacking/impress.js/node_modules/uglify-js/lib/parse-js.js:740:9)\n        at unexpected (/home/hingo/hacking/impress.js/node_modules/uglify-js/lib/parse-js.js:746:9)\n        at Object.semicolon [as 1] (/home/hingo/hacking/impress.js/node_modules/uglify-js/lib/parse-js.js:766:43)\n        at prog1 (/home/hingo/hacking/impress.js/node_modules/uglify-js/lib/parse-js.js:1314:21)\n        at simple_statement (/home/hingo/hacking/impress.js/node_modules/uglify-js/lib/parse-js.js:906:27)\n        at /home/hingo/hacking/impress.js/node_modules/uglify-js/lib/parse-js.js:814:19\n        at block_ (/home/hingo/hacking/impress.js/node_modules/uglify-js/lib/parse-js.js:1003:20)\n\nYou will be pleased to know, that the concatenation of the unminified file\n[js/impress.js](../../js/impress.js) has already succeeded at this point. Just\nopen a test in your browser, and the browser will show you the line and error.\n\n\n### Structure, naming and policy\n\nEach plugin is contained within its own directory. The name of the directory\nis the name of the plugin. For example, imagine a plugin called *pluginA*:\n\n    src/plugins/plugina/\n\nThe main javascript file should use the directory name as its root name:\n\n    src/plugins/plugina/plugina.js\n\nFor most plugins, a single `.js` file is enough.\n\nNote that the plugin name is also used as a namespace for various things. For\nexample, the *autoplay* plugin can be configured by setting the `data-autoplay=\"5\"`\nattribute on a `div`.\n\nAs a general rule ids, classes and attributes within the `div#impress` root\nelement, may use the plugin name directly (e.g. `data-autoplay=\"5\"`). However,\noutside of the root element, you should use `impress-pluginname` (e.g.\n`<div id=\"impress-toolbar\">`. The latter (longer) form also applies to all\nevents, they should be prefixed with `impress:pluginname`.\n\nYou should use crisp and descriptive names for your plugins. But\nsometimes you might optimize for a short namespace. Hence, the\n[Relative Positioning Plugin](rel/rel.js) is called `rel` to keep html attributes\nshort. You should not overuse this idea!\n\nNote that for default plugins, which is all plugins in this directory,\n**NO css, html or image files** are allowed.\n\nDefault plugins must not add any global variables.\n\n### Testing\n\nThe plugin directory should also include tests, which should use the *QUnit* and\n*Syn* libraries under [test/](../../test). You can have as many tests as you like,\nbut it is suggested your first and main test file is called `plugina_tests.html`\nand `plugina_tests.js` respectively. You need to add your test `.js` file into\n[/qunit_test_runner.html](../../qunit_test_runner.html), and the `.js` file \nshould start by loading the test `.html` file into the \n`iframe#presentation-iframe`. See [navigation-ui](navigation-ui) plugin for an \nexample.\n\nYou are allowed to test your plugin whatever way you like, but the general\napproach is for the test to load the [js/impress.js](../../js/impress.js) file\nproduced by build.js. This way you are testing what users will actually be\nusing, rather than the uncompiled source code.\n\nHowTo write a plugin\n--------------------\n\n### Encapsulation\n\nTo avoid polluting the global namespace, plugins must encapsulate them in the\nstandard javascript anonymous function:\n\n    /**\n     * Plugin A - An example plugin\n     *\n     * Description...\n     *\n     * Copyright 2016 Firstname Lastname, email or github handle\n     * Released under the MIT license.\n     */\n    (function ( document, window ) {\n\n        // Plugin implementation...\n\n    })(document, window);\n\n\n### Init plugins\n\nWe categorize plugins into various categories, based on how and when they are \ncalled, and what they do.\n\nAn init plugin is the simplest kind of plugin. It simply listens for the\n`impress().init()` method to send the `impress:init` event, at which point\nthe plugin can initialize itself and start doing whatever it does, for example \nby calling methods in the public api returned by `impress()`.\n\nThe `impress:init` event has the `div#impress` element as its `target` attribute,\nwhereas `event.detail.api` contains the same object that is returned by calling\n`impress()`. It is customary to store the api object sent by the event rather than\ncalling `impress()` from the global namespace.\n\nExample:\n\n    /**\n     * Plugin A - An example plugin\n     *\n     * Description...\n     *\n     * Copyright 2016 Firstname Lastname, email or github handle\n     * Released under the MIT license.\n     */\n    (function ( document, window ) {\n        var root;\n        var api;\n        var lib;\n\n        document.addEventListener( \"impress:init\", function( event ) {\n            root = event.target;\n            api = event.detail.api;\n            lib = api.lib;\n\n            // Element attributes starting with \"data-\", become available under\n            // element.dataset. In addition hyphenized words become camelCased.\n            var data = root.dataset;\n            // Get value of `<div id=\"impress\" data-plugina-foo=\"...\">`\n            var foo = data.pluginaFoo;\n            // ...\n        }\n    })(document, window);\n\n\nBoth [Navigation](navigation/navigation.js) and [Autoplay](autoplay/autoplay.js)\nare init plugins.\n\nTo provide end user configurability in your plugin, a good idea might be to\nread html attributes from the impress presentation. The\n[Autoplay](autoplay/autoplay.js) plugin does exactly this, you can provide\na default value in the `div#impress` element, or in each `div.step`.\n\nA plugin must only use html attributes in its designated namespace, which is\n\n    data-pluginName-*=\"value\"\n\nFor example, if *pluginA* offers config options `foo` and `bar`, it would look\nlike this:\n\n    <div id=\"impress\" data-plugina-foo=\"5\" data-plugina-bar=\"auto\" >\n\n\n### Pre-init plugins\n\nSome plugins need to run before even impress().init() does anything. These\nare typically *filters*: they want to modify the html via DOM calls, before\nimpress.js core parses the presentation. We call these *pre-init plugins*.\n\nA pre-init plugin must be called synchronously, before `impress().init()` is\nexecuted. Plugins can register themselves to be called in the pre-init phase\nby calling:\n\n    impress.addPreInitPlugin( plugin [, weight] );\n\nThe argument `plugin` must be a function. `weight` is optional and defaults to\n`10`. Plugins are ordered by weight when they are executed, with lower weight\nfirst.\n\nThe [Relative Positioning Plugin](rel/rel.js) is an example of a pre-init plugin.\n\n### Pre-StepLeave plugins\n\nA *pre-stepleave plugin* is called synchronously from impress.js core at the\nbeginning of `impress().goto()`. \n\nTo register a plugin, call\n\n    impress.addPreStepLeavePlugin( plugin [, weight] );\n\nWhen the plugin function is executed, it will be passed an argument\nthat resembles the `event` object from DOM event handlers:\n\n`event.target` contains the current step, which we are about to leave. \n\n`event.detail.next` contains the element we are about to transition to.\n\n`event.detail.reason` contains a string, one of \"next\", \"prev\" or \"goto\",\nwhich tells you which API function was called to initiate the transition.\n\n`event.detail.transitionDuration` contains the transitionDuration for the \nupcoming transition.\n\nA pre-stepleave plugin may alter the values in `event.detail` (except for \n`reason`), and this can change the behavior of the upcoming transition.\nFor example, the `goto` plugin will set the `event.detail.next` to point to\nsome other element, causing the presentation to jump to that step instead. \n\n\n### GUI plugins\n\nA *GUI plugin* is actually just an init plugin, but is a special category that\nexposes visible widgets or effects in the presentation. For example, it might\nprovide clickable buttons to go to the next and previous slide. \n\nNote that all plugins shipped in the default set **must not** produce any visible\nhtml elements unless the user asks for it. A recommended best practice is to let \nthe user add a div element, with an id equaling the plugin's namespace, in the \nplace where he wants to see whatever visual UI elements the plugin is providing:\n\n    <div id=\"impress-plugina\"></div>\n\nAnother way to show the elements of a UI plugin might be by allowing the user\nto explicitly press a key, like \"H\" for a help dialog.\n\n[Toolbar plugin](toolbar/README.md) is an example of a GUI plugin. It presents\na toolbar where other plugins can add their buttons in a centralized fashion.\n\nRemember that for default plugins, even GUI plugins, no html files, css files\nor images are allowed. Everything must be generated from javascript. The idea\nis that users can theme widgets with their own CSS. (A plugin is of course welcome\nto provide example CSS that can be copypasted :-)\n\nDependencies\n------------\n\nIf *pluginB* depends on the existence of *pluginA*, and also *pluginA* must run \nbefore *pluginB*, then *pluginB* should not listen to the `impress:init` event, \nrather *pluginA* should send its own init event, which *pluginB* listens to.\n\nExample:\n\n    // pluginA\n    document.addEventListener(\"impress:init\", function (event) {\n        // plugin A does it's own initialization first...\n\n        // Signal other plugins that plugin A is now initialized\n        var root = document.querySelector( \"div#impress\" );\n        var event = document.createEvent(\"CustomEvent\");\n        event.initCustomEvent(\"impress:plugina:init', true, true, { \"plugina\" : \"data...\" });\n        root.dispatchEvent(event);\n    }, false);\n    \n    // pluginB\n    document.addEventListener(\"impress:plugina:init\", function (event) {\n        // plugin B implementation\n    }, false);\n\nA plugin should use the namespace `impress:pluginname:*` for any events it sends.\n\nIn theory all plugins could always send an `init` and other events, but in\npractice we're adding them on an as needed basis.\n"
  },
  {
    "path": "src/plugins/autoplay/README.md",
    "content": "# Autoplay\n\nThe [autoplay](/src/plugins/autoplay/autoplay.js) plugin automatically advances the presentation after a certain timeout expired. \n\n## USAGE\n\nYou first have to enable the plugin by setting a global ```data-autoplay``` value on the impress-div. Then you can change individual ```data-autoplay``` values on each *step* by adding ```data-autoplay``` to it. If this value is set to ```0```, there will be no more auto-advancing on this *step*. The value you enter is time in seconds to switch to the next slide.\n\n## EXAMPLE\n\nNote: This only shows part of the HTML. If you want to know how to set up a presentation, I highly recommend you read our [Getting Started Guide](/GettingStarted.md)\n```\n<div id=impress data-autoplay=\"5\">\n    <div class=\"step\" data-autoplay=\"0\">\n        This slide will not auto-advance\n    </div>\n     <div class=\"step\">\n        This slide will auto-advance at the globally defined rate.\n    </div>\n     <div class=\"step\" data-autoplay=\"10\">\n        This slide will auto-advance after 10 seconds\n    </div>\n</div>\n```"
  },
  {
    "path": "src/plugins/autoplay/autoplay.js",
    "content": "/**\n * Autoplay plugin - Automatically advance slideshow after N seconds\n *\n * Copyright 2016 Henrik Ingo, henrik.ingo@avoinelama.fi\n * Released under the MIT license.\n */\n/* global clearTimeout, setTimeout, document */\n\n( function( document ) {\n    \"use strict\";\n\n    var autoplayDefault = 0;\n    var currentStepTimeout = 0;\n    var api = null;\n    var timeoutHandle = null;\n    var root = null;\n    var util;\n\n    // On impress:init, check whether there is a default setting, as well as\n    // handle step-1.\n    document.addEventListener( \"impress:init\", function( event ) {\n        util = event.detail.api.lib.util;\n\n        // Getting API from event data instead of global impress().init().\n        // You don't even need to know what is the id of the root element\n        // or anything. `impress:init` event data gives you everything you\n        // need to control the presentation that was just initialized.\n        api = event.detail.api;\n        root = event.target;\n\n        // Element attributes starting with \"data-\", become available under\n        // element.dataset. In addition hyphenized words become camelCased.\n        var data = root.dataset;\n        var autoplay = util.getUrlParamValue( \"impress-autoplay\" ) || data.autoplay;\n\n        if ( autoplay ) {\n            autoplayDefault = util.toNumber( autoplay, 0 );\n        }\n\n        var toolbar = document.querySelector( \"#impress-toolbar\" );\n        if ( toolbar ) {\n            addToolbarButton( toolbar );\n        }\n\n        api.lib.gc.pushCallback( function() {\n            clearTimeout( timeoutHandle );\n        } );\n\n        // Note that right after impress:init event, also impress:stepenter is\n        // triggered for the first slide, so that's where code flow continues.\n    }, false );\n\n    document.addEventListener( \"impress:autoplay:pause\", function( event ) {\n        status = \"paused\";\n        reloadTimeout( event );\n    }, false );\n\n    document.addEventListener( \"impress:autoplay:play\", function( event ) {\n        status = \"playing\";\n        reloadTimeout( event );\n    }, false );\n\n    // If default autoplay time was defined in the presentation root, or\n    // in this step, set timeout.\n    var reloadTimeout = function( event ) {\n        var step = event.target;\n        currentStepTimeout = util.toNumber( step.dataset.autoplay, autoplayDefault );\n        if ( status === \"paused\" ) {\n            setAutoplayTimeout( 0 );\n        } else {\n            setAutoplayTimeout( currentStepTimeout );\n        }\n    };\n\n    document.addEventListener( \"impress:stepenter\", function( event ) {\n        reloadTimeout( event );\n    }, false );\n\n    document.addEventListener( \"impress:substep:enter\", function( event ) {\n        reloadTimeout( event );\n    }, false );\n\n    /**\n     * Set timeout after which we move to next() step.\n     */\n    var setAutoplayTimeout = function( timeout ) {\n        if ( timeoutHandle ) {\n            clearTimeout( timeoutHandle );\n        }\n\n        if ( timeout > 0 ) {\n            timeoutHandle = setTimeout( function() { api.next(); }, timeout * 1000 );\n        }\n        setButtonText();\n    };\n\n    /*** Toolbar plugin integration *******************************************/\n    var status = \"not clicked\";\n    var toolbarButton = null;\n\n    var makeDomElement = function( html ) {\n        var tempDiv = document.createElement( \"div\" );\n        tempDiv.innerHTML = html;\n        return tempDiv.firstChild;\n    };\n\n    var toggleStatus = function() {\n        if ( currentStepTimeout > 0 && status !== \"paused\" ) {\n            status = \"paused\";\n        } else {\n            status = \"playing\";\n        }\n    };\n\n    var getButtonText = function() {\n        if ( currentStepTimeout > 0 && status !== \"paused\" ) {\n            return \"||\"; // Pause\n        } else {\n            return \"&#9654;\"; // Play\n        }\n    };\n\n    var setButtonText = function() {\n        if ( toolbarButton ) {\n\n            // Keep button size the same even if label content is changing\n            var buttonWidth = toolbarButton.offsetWidth;\n            var buttonHeight = toolbarButton.offsetHeight;\n            toolbarButton.innerHTML = getButtonText();\n            if ( !toolbarButton.style.width ) {\n                toolbarButton.style.width = buttonWidth + \"px\";\n            }\n            if ( !toolbarButton.style.height ) {\n                toolbarButton.style.height = buttonHeight + \"px\";\n            }\n        }\n    };\n\n    var addToolbarButton = function( toolbar ) {\n        var html = '<button id=\"impress-autoplay-playpause\" ' + // jshint ignore:line\n                   'title=\"Autoplay\" class=\"impress-autoplay\">' + // jshint ignore:line\n                   getButtonText() + \"</button>\"; // jshint ignore:line\n        toolbarButton = makeDomElement( html );\n        toolbarButton.addEventListener( \"click\", function() {\n            toggleStatus();\n            if ( status === \"playing\" ) {\n                if ( autoplayDefault === 0 ) {\n                    autoplayDefault = 7;\n                }\n                if ( currentStepTimeout === 0 ) {\n                    currentStepTimeout = autoplayDefault;\n                }\n                setAutoplayTimeout( currentStepTimeout );\n            } else if ( status === \"paused\" ) {\n                setAutoplayTimeout( 0 );\n            }\n        } );\n\n        util.triggerEvent( toolbar, \"impress:toolbar:appendChild\",\n                      { group: 10, element: toolbarButton } );\n    };\n\n} )( document );\n"
  },
  {
    "path": "src/plugins/blackout/README.md",
    "content": "# Blackout\n\nThis plugin is automatically enabled and runs whenever you start your presentation. You can press *B* or *.* on your keyboard to blank / unblank the screen."
  },
  {
    "path": "src/plugins/blackout/blackout.js",
    "content": "/**\n * Blackout plugin\n *\n * Press b or . to hide all slides, and b or . again to show them.\n * Also navigating to a different slide will show them again (impress:stepleave).\n *\n * Copyright 2014 @Strikeskids\n * Released under the MIT license.\n */\n/* global document */\n\n( function( document ) {\n    \"use strict\";\n\n    var canvas = null;\n    var blackedOut = false;\n    var util = null;\n    var root = null;\n    var api = null;\n\n    // While waiting for a shared library of utilities, copying these 2 from main impress.js\n    var css = function( el, props ) {\n        var key, pkey;\n        for ( key in props ) {\n            if ( props.hasOwnProperty( key ) ) {\n                pkey = pfx( key );\n                if ( pkey !== null ) {\n                    el.style[ pkey ] = props[ key ];\n                }\n            }\n        }\n        return el;\n    };\n\n    var pfx = ( function() {\n\n        var style = document.createElement( \"dummy\" ).style,\n            prefixes = \"Webkit Moz O ms Khtml\".split( \" \" ),\n            memory = {};\n\n        return function( prop ) {\n            if ( typeof memory[ prop ] === \"undefined\" ) {\n\n                var ucProp  = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ),\n                    props   = ( prop + \" \" + prefixes.join( ucProp + \" \" ) + ucProp ).split( \" \" );\n\n                memory[ prop ] = null;\n                for ( var i in props ) {\n                    if ( style[ props[ i ] ] !== undefined ) {\n                        memory[ prop ] = props[ i ];\n                        break;\n                    }\n                }\n\n            }\n\n            return memory[ prop ];\n        };\n\n    } )();\n\n    var removeBlackout = function() {\n        if ( blackedOut ) {\n            css( canvas, {\n                display: \"block\"\n            } );\n            blackedOut = false;\n            util.triggerEvent( root, \"impress:autoplay:play\", {} );\n        }\n    };\n\n    var blackout = function() {\n        if ( blackedOut ) {\n            removeBlackout();\n        } else {\n            css( canvas, {\n                display: ( blackedOut = !blackedOut ) ? \"none\" : \"block\"\n            } );\n            blackedOut = true;\n            util.triggerEvent( root, \"impress:autoplay:pause\", {} );\n        }\n    };\n\n    // Wait for impress.js to be initialized\n    document.addEventListener( \"impress:init\", function( event ) {\n        api = event.detail.api;\n        util = api.lib.util;\n        root = event.target;\n        canvas = root.firstElementChild;\n        var gc = api.lib.gc;\n\n        gc.addEventListener( document, \"keydown\", function( event ) {\n\n            // Accept b or . -> . is sent by presentation remote controllers\n            if ( event.keyCode === 66 || event.keyCode === 190 ) {\n                event.preventDefault();\n                if ( !blackedOut ) {\n                    blackout();\n                } else {\n                    removeBlackout();\n                }\n            }\n        }, false );\n\n        gc.addEventListener( document, \"keyup\", function( event ) {\n\n            // Accept b or . -> . is sent by presentation remote controllers\n            if ( event.keyCode === 66 || event.keyCode === 190 ) {\n                event.preventDefault();\n            }\n        }, false );\n\n    }, false );\n\n    document.addEventListener( \"impress:stepleave\", function() {\n        removeBlackout();\n    }, false );\n\n} )( document );\n\n"
  },
  {
    "path": "src/plugins/bookmark/README.md",
    "content": "# Bookmark\n\nNonlinear navigation similar to the Goto plugin.\n\nGoto supports nonlinear navigation by *locally* defining *out-links*, accessible via the arrow keys.\n\nBookmark supports nonlinear navigation by *globally* defining *in-links*, accessible via normal keys like 1,2,3,A,B,C.\n\nExample:\n\n```html\n<!-- data-bookmark-key-list allows an \"inbound\"-oriented style of non-linear navigation. -->\n<div id=\"...\" class=\"step\" data-bookmark-key-list=\"Digit1 KeyA 1 2 3 a b c\">\n```\n\nAn `id` is required on the `div`.\n\nIf you assign the same key to multiple steps, that hotkey will cycle among them.\n\nWARNING: It's up to you to avoid reserved hotkeys H, B, P, ?, etc.\n\nAuthor\n------\n\nCopyright 2023 Wong Meng Weng (@mengwong)\nReleased under the MIT license.\n\n"
  },
  {
    "path": "src/plugins/bookmark/bookmark.js",
    "content": "/**\n * Bookmark Plugin\n *\n * The bookmark plugin consists of\n *   a pre-init plugin,\n *   a keyup listener, and\n *   a pre-stepleave plugin.\n *\n * The pre-init plugin surveys all step divs to set up bookmark keybindings.\n * The pre-stepleave plugin alters the destination when a bookmark hotkey is pressed.\n *\n * Example:\n *\n *       <!-- data-bookmark-key-list allows an \"inbound\" style of non-linear navigation. -->\n *       <div id=\"...\" class=\"step\" data-bookmark-key-list=\"Digit1 KeyA 1 2 3 a b c\">\n *\n * See https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values for a table\n * of what strings to use for each key. Both .key and .code styles are recognized.\n *\n * It's up to the HTML author to avoid reserved hotkeys H, B, P, ? etc.\n *\n * Copyright 2016-2017 Henrik Ingo (@henrikingo)\n * Released under the MIT license.\n */\n/* global document, impress */\n\n( function( document ) {\n    \"use strict\";\n    var hotkeys = {};\n    function hotkeyDest( event ) {\n\treturn ( hotkeys.hasOwnProperty( event.key )  ? hotkeys[ event.key ] :\n\t\t hotkeys.hasOwnProperty( event.code ) ? hotkeys[ event.code ] : null ); }\n\n    // In pre-init phase, build a map of bookmark hotkey to div id, by reviewing all steps\n    impress.addPreInitPlugin( function( root, api ) {\n\troot.querySelectorAll( \".step\" ).forEach( function( div ) {\n            if ( div.dataset.bookmarkKeyList !== undefined && div.id !== undefined ) {\n\t\tdiv.dataset.bookmarkKeyList.split( \" \" ).forEach( ( k ) => {\n\t\t    if ( hotkeys.hasOwnProperty( k ) ) {\n\t\t\thotkeys[ k ].push( div.id );\n\t\t    } else { hotkeys[ k ] = [ div.id ]; } } ); } } );\n\n\tapi.lib.gc.addEventListener( document, \"keyup\", function( event ) {\n\t    if ( hotkeyDest( event ) !== null ) {\n\t\tevent.stopImmediatePropagation();\n\t\tapi.next( event );\n\n\t\t// Event.preventDefault();\n\t    }\n\t} );\n    } );\n\n    // In pre-stepleave phase, match a hotkey and reset destination accordingly.\n    impress.addPreStepLeavePlugin( function( event ) {\n\n\t// Window.console.log(`bookmark: running as PreStepLeavePlugin; event=`);\n\t// window.console.log(event)\n        if ( ( !event || !event.origEvent ) ) { return; }\n\tvar dest = hotkeyDest( event.origEvent );\n        if ( dest ) {\n\n\t    // Window.console.log(`bookmark: recognizing hotkey ${event.code} goes to ${dest}`)\n            var newTarget = document.getElementById( dest[ 0 ] ); // jshint ignore:line\n            if ( newTarget ) {\n                event.detail.next = newTarget;\n\t\tdest.push( dest.shift() ); // Repeated hotkey presses cycle through each dest.\n            }\n        }\n    } );\n\n} )( document );\n\n"
  },
  {
    "path": "src/plugins/extras/README.md",
    "content": "Extras Plugin\n=============\n\nThe Extras plugin will initialize the optional addon plugins from \n[extras/](../../../extras/) directory, if they were loaded.\n\nGenerally, for an extras plugin to have been loaded, 2 things must have happened:\n\n1. The extras plugins must be present in extras/ directory, for example after \n   running `git submodule update`\n2. One or more extras plugins are added to the impress.js presentation (the HTML\n   file) by the author using a regular `<script>` tag.\n\nIf one or more extras plugins were so added, this plugin will automatically\ndiscover them and perform initialization (such as calling \n`mermaid.initialize()`).\n\nIf no extras plugins are added to a presentation, this plugin does nothing.\n\nNote that some extra plugins (like mathjax) initialize themselves immediately, and\nthere's nothing to do here.\n\nAuthor\n------\n\nHenrik Ingo (@henrikingo), 2016\n"
  },
  {
    "path": "src/plugins/extras/extras.js",
    "content": "/**\n * Extras Plugin\n *\n * This plugin performs initialization (like calling mermaid.initialize())\n * for the extras/ plugins if they are loaded into a presentation.\n *\n * See README.md for details.\n *\n * Copyright 2016 Henrik Ingo (@henrikingo)\n * Released under the MIT license.\n */\n/* global markdown, marked, hljs, mermaid, impress */\n\n( function( document, window ) {\n    \"use strict\";\n\n    const SLIDE_SEPARATOR = /^-----$/m;\n\n    const getMarkdownParser = function( ) {\n        if ( window.hasOwnProperty( \"marked\" ) ) {\n\n            // Using marked\n            return function( elem, src ) {\n                return marked.parse( src );\n            };\n        } else if ( window.hasOwnProperty( \"markdown\" ) ) {\n\n            // Using builtin markdown engine\n            return function( elem, src ) {\n                var dialect = elem.dataset.markdownDialect;\n                return markdown.toHTML( src, dialect );\n            };\n        }\n\n        return null;\n    };\n\n    const getMarkdownSlides = function( elem ) {\n        var text = elem.textContent;\n\n        // Using first not blank line to detect leading whitespaces.\n        // can't properly handle the mixing of space and tabs\n        var m = text.match( /^([ \\t]*)\\S/m );\n        if ( m !== null ) {\n            text = text.replace( new RegExp( \"^\" + m[ 1 ], \"mg\" ), \"\" );\n        }\n\n        return text.split( SLIDE_SEPARATOR );\n    };\n\n    const convertMarkdowns = function( selector ) {\n\n        // Detect markdown engine\n        var parseMarkdown = getMarkdownParser();\n        if ( !parseMarkdown ) {\n            return;\n        }\n\n        for ( var elem of document.querySelectorAll( selector ) ) {\n            var id = null;\n            if ( elem.id ) {\n                id = elem.id;\n                elem.id = \"\";\n            }\n\n            var origTitle = null;\n            if ( elem.title ) {\n                origTitle = elem.title;\n                elem.title = \"\";\n            }\n\n            var slides = getMarkdownSlides( elem );\n            var slideElems = [ elem ];\n\n            for ( var j = 1; j < slides.length; ++j ) {\n                var newElem = elem.cloneNode( false );\n                newElem.id = \"\";\n                elem.parentNode.insertBefore( newElem, slideElems[ 0 ] );\n                slideElems.splice( 0, 0, newElem );\n            }\n\n            if ( id ) {\n                slideElems[ 0 ].id = id;\n            }\n\n            for ( var i = 0; i < slides.length; ++i ) {\n                slideElems[ i ].innerHTML =\n                    parseMarkdown( slideElems[ i ], slides[ i ] );\n\n                if ( origTitle && ( i === 0 ) ) {\n                    slideElems[ i ].title = origTitle;\n                }\n            }\n        }\n    };\n\n    var preInit = function() {\n\n        // Query all .markdown elements and translate to HTML\n        convertMarkdowns( \".markdown\" );\n\n        if ( window.hljs ) {\n            hljs.initHighlightingOnLoad();\n        }\n\n        if ( window.mermaid ) {\n            mermaid.initialize( { startOnLoad:true } );\n        }\n    };\n\n    // Register the plugin to be called in pre-init phase\n    // Note: Markdown.js should run early/first, because it creates new div elements.\n    // So add this with a lower-than-default weight.\n    impress.addPreInitPlugin( preInit, 1 );\n\n} )( document, window );\n\n"
  },
  {
    "path": "src/plugins/form/README.md",
    "content": "# Form\n\nForm support! Functionality to better support use of input, textarea, button... elements in a presentation.\n\nThis plugin does two things:\n\nSet stopPropagation on any element that might take text input. This allows users to type, for example, the letter 'P' into a form field, without causing the presenter console to spring up.\n \nOn impress:stepleave, de-focus any potentially active element. This is to prevent the focus from being left in a form element that is no longer visible in the window, and user therefore typing garbage into the form.\n\n***THIS PLUGIN REQUIRES FURTHER DEVELOPMENT***\n\n TODO: Currently it is not possible to use TAB to navigate between form elements. Impress.js, and\n in particular the navigation plugin, unfortunately must fully take control of the tab key,\n otherwise a user could cause the browser to scroll to a link or button that's not on the current\n step. However, it could be possible to allow tab navigation between form elements, as long as\n they are on the active step. This is a topic for further study."
  },
  {
    "path": "src/plugins/form/form.js",
    "content": "/**\n * Form support\n *\n * Functionality to better support use of input, textarea, button... elements in a presentation.\n *\n * This plugin does two things:\n *\n * Set stopPropagation on any element that might take text input. This allows users to type, for\n * example, the letter 'P' into a form field, without causing the presenter console to spring up.\n *\n * On impress:stepleave, de-focus any potentially active\n * element. This is to prevent the focus from being left in a form element that is no longer visible\n * in the window, and user therefore typing garbage into the form.\n *\n * TODO: Currently it is not possible to use TAB to navigate between form elements. Impress.js, and\n * in particular the navigation plugin, unfortunately must fully take control of the tab key,\n * otherwise a user could cause the browser to scroll to a link or button that's not on the current\n * step. However, it could be possible to allow tab navigation between form elements, as long as\n * they are on the active step. This is a topic for further study.\n *\n * Copyright 2016 Henrik Ingo\n * MIT License\n */\n/* global document */\n( function( document ) {\n    \"use strict\";\n    var root;\n    var api;\n\n    document.addEventListener( \"impress:init\", function( event ) {\n        root = event.target;\n        api = event.detail.api;\n        var gc = api.lib.gc;\n\n        var selectors = [ \"input\", \"textarea\", \"select\", \"[contenteditable=true]\" ];\n        for ( var selector of selectors ) {\n            var elements = document.querySelectorAll( selector );\n            if ( !elements ) {\n                continue;\n            }\n\n            for ( var i = 0; i < elements.length; i++ ) {\n                var e = elements[ i ];\n                gc.addEventListener( e, \"keydown\", function( event ) {\n                    event.stopPropagation();\n                } );\n                gc.addEventListener( e, \"keyup\", function( event ) {\n                    event.stopPropagation();\n                } );\n            }\n        }\n    }, false );\n\n    document.addEventListener( \"impress:stepleave\", function() {\n        document.activeElement.blur();\n    }, false );\n\n} )( document );\n\n"
  },
  {
    "path": "src/plugins/fullscreen/README.md",
    "content": "# Fullscreen\n\nThis plugin puts your presentation into fullscreen by pressing *F5*. You can leave fullscreen again by pressing *Esc*. \n\n*Note:* impress.js works just fine with the normal fullscreen offered by your browser where you would (usually) press *F11* to enter it. There are certain circumstances where you might want to use this plugin instead, as it should work with the impressConsole plugin as well.\n"
  },
  {
    "path": "src/plugins/fullscreen/fullscreen.js",
    "content": "/**\n * Fullscreen plugin\n *\n * Press F5 to enter fullscreen and ESC to exit fullscreen mode.\n *\n * Copyright 2019 @giflw\n * Released under the MIT license.\n */\n/* global document */\n\n( function( document ) {\n    \"use strict\";\n\n    function enterFullscreen() {\n        var elem = document.documentElement;\n        if ( !document.fullscreenElement ) {\n            elem.requestFullscreen();\n        }\n    }\n\n    function exitFullscreen() {\n        if ( document.fullscreenElement ) {\n            document.exitFullscreen();\n        }\n    }\n\n    // Wait for impress.js to be initialized\n    document.addEventListener( \"impress:init\", function( event ) {\n        var api = event.detail.api;\n        var root = event.target;\n        var gc = api.lib.gc;\n        var util = api.lib.util;\n\n        gc.addEventListener( document, \"keydown\", function( event ) {\n\n            // 116 (F5) is sent by presentation remote controllers\n            if ( event.code === \"F5\" ) {\n                event.preventDefault();\n                enterFullscreen();\n                util.triggerEvent( root.querySelector( \".active\" ), \"impress:steprefresh\" );\n            }\n\n            // 27 (Escape) is sent by presentation remote controllers\n            if ( event.key === \"Escape\" || event.key === \"F5\" ) {\n                event.preventDefault();\n                exitFullscreen();\n                util.triggerEvent( root.querySelector( \".active\" ), \"impress:steprefresh\" );\n            }\n        }, false );\n\n        util.triggerEvent( document, \"impress:help:add\",\n            { command: \"F5 / ESC\", text: \"Fullscreen: Enter / Exit\", row: 200 } );\n\n    }, false );\n\n} )( document );\n\n"
  },
  {
    "path": "src/plugins/goto/README.md",
    "content": "Goto Plugin\n===========\n\nThe goto plugin is a pre-stepleave plugin. It is executed before \n`impress:stepleave` event, and will alter the destination where to transition next.\n\nExample:\n\n        <!-- When leaving this step, go directly to \"step-5\" -->\n        <div class=\"step\" data-goto=\"step-5\">\n\n        <!-- When leaving this step with next(), go directly to \"step-5\", instead of the next step.\n             If moving backwards to previous step - e.g. prev() instead of next() - then go to \"step-1\". -->\n        <div class=\"step\" data-goto-next=\"step-5\" data-goto-prev=\"step-1\">\n\n        <!-- data-goto-key-list and data-goto-next-list allow you to build advanced non-linear navigation. -->\n        <div class=\"step\" data-goto-key-list=\"ArrowUp ArrowDown ArrowRight ArrowLeft\" data-goto-next-list=\"step-4 step-3 step-2 step-5\">\n\nSee https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values for a table\nof what strings to use for each key.\n\n Author\n------\n\nCopyright 2016 Henrik Ingo (@henrikingo)\nReleased under the MIT license.\n\n"
  },
  {
    "path": "src/plugins/goto/goto.js",
    "content": "/**\n * Goto Plugin\n *\n * The goto plugin is a pre-stepleave plugin. It is executed before impress:stepleave,\n * and will alter the destination where to transition next.\n *\n * Example:\n *\n *         <!-- When leaving this step, go directly to \"step-5\" -->\n *         <div class=\"step\" data-goto=\"step-5\">\n *\n *         <!-- When leaving this step with next(), go directly to \"step-5\", instead of next step.\n *              If moving backwards to previous step - e.g. prev() instead of next() -\n *              then go to \"step-1\". -->\n *         <div class=\"step\" data-goto-next=\"step-5\" data-goto-prev=\"step-1\">\n *\n *        <!-- data-goto-key-list and data-goto-next-list allow you to build advanced non-linear\n *             navigation. -->\n *        <div class=\"step\"\n *             data-goto-key-list=\"ArrowUp ArrowDown ArrowRight ArrowLeft\"\n *             data-goto-next-list=\"step-4 step-3 step-2 step-5\">\n *\n * See https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values for a table\n * of what strings to use for each key.\n *\n * Copyright 2016-2017 Henrik Ingo (@henrikingo)\n * Released under the MIT license.\n */\n/* global window, document, impress */\n\n( function( document, window ) {\n    \"use strict\";\n    var lib;\n\n    document.addEventListener( \"impress:init\", function( event ) {\n        lib = event.detail.api.lib;\n    }, false );\n\n    var isNumber = function( numeric ) {\n        return !isNaN( numeric );\n    };\n\n    var goto = function( event ) {\n        if ( ( !event ) || ( !event.target ) ) {\n            return;\n        }\n\n        var data = event.target.dataset;\n        var steps = document.querySelectorAll( \".step\" );\n\n        // Data-goto-key-list=\"\" & data-goto-next-list=\"\" //////////////////////////////////////////\n        if ( data.gotoKeyList !== undefined &&\n             data.gotoNextList !== undefined &&\n             event.origEvent !== undefined &&\n             event.origEvent.key !== undefined ) {\n            var keylist = data.gotoKeyList.split( \" \" );\n            var nextlist = data.gotoNextList.split( \" \" );\n\n            if ( keylist.length !== nextlist.length ) {\n                window.console.log(\n                    \"impress goto plugin: data-goto-key-list and data-goto-next-list don't match:\"\n                );\n                window.console.log( keylist );\n                window.console.log( nextlist );\n\n                // Don't return, allow the other categories to work despite this error\n            } else {\n                var index = keylist.indexOf( event.origEvent.key );\n                if ( index >= 0 ) {\n                    var next = nextlist[ index ];\n                    if ( isNumber( next ) ) {\n                        event.detail.next = steps[ next ];\n\n                        // If the new next element has its own transitionDuration, we're responsible\n                        // for setting that on the event as well\n                        event.detail.transitionDuration = lib.util.toNumber(\n                            event.detail.next.dataset.transitionDuration,\n                            event.detail.transitionDuration\n                        );\n                        return;\n                    } else {\n                        var newTarget = document.getElementById( next );\n                        if ( newTarget && newTarget.classList.contains( \"step\" ) ) {\n                            event.detail.next = newTarget;\n                            event.detail.transitionDuration = lib.util.toNumber(\n                                event.detail.next.dataset.transitionDuration,\n                                event.detail.transitionDuration\n                            );\n                            return;\n                        } else {\n                            window.console.log( \"impress goto plugin: \" + next +\n                                                \" is not a step in this impress presentation.\" );\n                        }\n                    }\n                }\n            }\n        }\n\n        // Data-goto-next=\"\" & data-goto-prev=\"\" ///////////////////////////////////////////////////\n\n        // Handle event.target data-goto-next attribute\n        if ( isNumber( data.gotoNext ) && event.detail.reason === \"next\" ) {\n            event.detail.next = steps[ data.gotoNext ];\n\n            // If the new next element has its own transitionDuration, we're responsible for setting\n            // that on the event as well\n            event.detail.transitionDuration = lib.util.toNumber(\n                event.detail.next.dataset.transitionDuration, event.detail.transitionDuration\n            );\n            return;\n        }\n        if ( data.gotoNext && event.detail.reason === \"next\" ) {\n            var newTarget = document.getElementById( data.gotoNext ); // jshint ignore:line\n            if ( newTarget && newTarget.classList.contains( \"step\" ) ) {\n                event.detail.next = newTarget;\n                event.detail.transitionDuration = lib.util.toNumber(\n                    event.detail.next.dataset.transitionDuration,\n                    event.detail.transitionDuration\n                );\n                return;\n            } else {\n                window.console.log( \"impress goto plugin: \" + data.gotoNext +\n                                    \" is not a step in this impress presentation.\" );\n            }\n        }\n\n        // Handle event.target data-goto-prev attribute\n        if ( isNumber( data.gotoPrev ) && event.detail.reason === \"prev\" ) {\n            event.detail.next = steps[ data.gotoPrev ];\n            event.detail.transitionDuration = lib.util.toNumber(\n                event.detail.next.dataset.transitionDuration, event.detail.transitionDuration\n            );\n            return;\n        }\n        if ( data.gotoPrev && event.detail.reason === \"prev\" ) {\n            var newTarget = document.getElementById( data.gotoPrev ); // jshint ignore:line\n            if ( newTarget && newTarget.classList.contains( \"step\" ) ) {\n                event.detail.next = newTarget;\n                event.detail.transitionDuration = lib.util.toNumber(\n                    event.detail.next.dataset.transitionDuration, event.detail.transitionDuration\n                );\n                return;\n            } else {\n                window.console.log( \"impress goto plugin: \" + data.gotoPrev +\n                                    \" is not a step in this impress presentation.\" );\n            }\n        }\n\n        // Data-goto=\"\" ///////////////////////////////////////////////////////////////////////////\n\n        // Handle event.target data-goto attribute\n        if ( isNumber( data.goto ) ) {\n            event.detail.next = steps[ data.goto ];\n            event.detail.transitionDuration = lib.util.toNumber(\n                event.detail.next.dataset.transitionDuration, event.detail.transitionDuration\n            );\n            return;\n        }\n        if ( data.goto ) {\n            var newTarget = document.getElementById( data.goto ); // jshint ignore:line\n            if ( newTarget && newTarget.classList.contains( \"step\" ) ) {\n                event.detail.next = newTarget;\n                event.detail.transitionDuration = lib.util.toNumber(\n                    event.detail.next.dataset.transitionDuration, event.detail.transitionDuration\n                );\n                return;\n            } else {\n                window.console.log( \"impress goto plugin: \" + data.goto +\n                                    \" is not a step in this impress presentation.\" );\n            }\n        }\n    };\n\n    // Register the plugin to be called in pre-stepleave phase\n    impress.addPreStepLeavePlugin( goto );\n\n} )( document, window );\n\n"
  },
  {
    "path": "src/plugins/help/README.md",
    "content": "Help screen plugin\n===================\n\nShows a help popup when a presentation is loaded, as well as when 'H' is pressed.\n\nTo enable the help popup, add following div to your presentation:\n\n    <div id=\"impress-help\"></div>\n\nExample CSS:\n\n        .impress-enabled #impress-help {\n            background: none repeat scroll 0 0 rgba(0, 0, 0, 0.5);\n            color: #EEEEEE;\n            font-size: 80%;\n            position: fixed;\n            left: 2em;\n            bottom: 2em;\n            width: 24em;\n            border-radius: 1em;\n            padding: 1em;\n            text-align: center;\n            z-index: 100;\n            font-family: Verdana, Arial, Sans;\n        }\n        .impress-enabled #impress-help td {\n            padding-left: 1em;\n            padding-right: 1em;\n        }\n\n\n\nAuthor\n------\n\nCopyright Henrik Ingo (@henrikingo), 2016\nMIT License\n"
  },
  {
    "path": "src/plugins/help/help.js",
    "content": "/**\n * Help popup plugin\n *\n * Example:\n *\n *     <!-- Show a help popup at start, or if user presses \"H\" -->\n *     <div id=\"impress-help\"></div>\n *\n * For developers:\n *\n * Typical use for this plugin, is for plugins that support some keypress, to add a line\n * to the help popup produced by this plugin. For example \"P: Presenter console\".\n *\n * Copyright 2016 Henrik Ingo (@henrikingo)\n * Released under the MIT license.\n */\n/* global window, document */\n\n( function( document, window ) {\n    \"use strict\";\n    var rows = [];\n    var timeoutHandle;\n\n    var triggerEvent = function( el, eventName, detail ) {\n        var event = document.createEvent( \"CustomEvent\" );\n        event.initCustomEvent( eventName, true, true, detail );\n        el.dispatchEvent( event );\n    };\n\n    var renderHelpDiv = function() {\n        var helpDiv = document.getElementById( \"impress-help\" );\n        if ( helpDiv ) {\n            var html = [];\n            for ( var row in rows ) {\n                for ( var arrayItem in row ) {\n                    html.push( rows[ row ][ arrayItem ] );\n                }\n            }\n            if ( html ) {\n                helpDiv.innerHTML = \"<table>\\n\" + html.join( \"\\n\" ) + \"</table>\\n\";\n            }\n        }\n    };\n\n    var toggleHelp = function() {\n        var helpDiv = document.getElementById( \"impress-help\" );\n        if ( !helpDiv ) {\n            return;\n        }\n\n        if ( helpDiv.style.display === \"block\" ) {\n            helpDiv.style.display = \"none\";\n        } else {\n            helpDiv.style.display = \"block\";\n            window.clearTimeout( timeoutHandle );\n        }\n    };\n\n    document.addEventListener( \"keyup\", function( event ) {\n\n        if ( event.keyCode === 72 || event.keyCode === 191 ) { // \"h\" || \"?\"\n            event.preventDefault();\n            toggleHelp();\n        }\n    }, false );\n\n    // API\n    // Other plugins can add help texts, typically if they support an action on a keypress.\n    /**\n     * Add a help text to the help popup.\n     *\n     * :param: e.detail.command  Example: \"H\"\n     * :param: e.detail.text     Example: \"Show this help.\"\n     * :param: e.detail.row      Row index from 0 to 9 where to place this help text. Example: 0\n     */\n    document.addEventListener( \"impress:help:add\", function( e ) {\n\n        // The idea is for the sender of the event to supply a unique row index, used for sorting.\n        // But just in case two plugins would ever use the same row index, we wrap each row into\n        // its own array. If there are more than one entry for the same index, they are shown in\n        // first come, first serve ordering.\n        var rowIndex = e.detail.row;\n        if ( typeof rows[ rowIndex ] !== \"object\" || !rows[ rowIndex ].isArray ) {\n            rows[ rowIndex ] = [];\n        }\n        rows[ e.detail.row ].push( \"<tr><td><strong>\" + e.detail.command + \"</strong></td><td>\" +\n                                   e.detail.text + \"</td></tr>\" );\n        renderHelpDiv();\n    } );\n\n    document.addEventListener( \"impress:init\", function( e ) {\n        renderHelpDiv();\n\n        // At start, show the help for 7 seconds.\n        var helpDiv = document.getElementById( \"impress-help\" );\n        if ( helpDiv ) {\n            helpDiv.style.display = \"block\";\n            timeoutHandle = window.setTimeout( function() {\n                var helpDiv = document.getElementById( \"impress-help\" );\n                helpDiv.style.display = \"none\";\n            }, 7000 );\n\n            // Regster callback to empty the help div on teardown\n            var api = e.detail.api;\n            api.lib.gc.pushCallback( function() {\n                window.clearTimeout( timeoutHandle );\n                helpDiv.style.display = \"\";\n                helpDiv.innerHTML = \"\";\n                rows = [];\n            } );\n        }\n\n        // Use our own API to register the help text for \"h\"\n        triggerEvent( document, \"impress:help:add\",\n                      { command: \"H\", text: \"Show this help\", row: 0 } );\n    } );\n\n} )( document, window );\n\n"
  },
  {
    "path": "src/plugins/impressConsole/README.md",
    "content": "Impress Console Plugin\n======================\n\nPress 'P' to show a speaker console window.\n\n* View of current slide\n* Preview of next slide\n* Speaker notes (contents of a <div class=\"notes\"> element on current slide)\n* Navigation\n\nFor speaker notes, add the following anywhere inside a step\n\n    <div class=\"notes\">Speaker notes text...</div>\n\nExample CSS:\n\n    /* Hide notes from the actual presentation. This will not affect the visibility of notes in the impress console window. */\n\n    .notes {\n        display: none;\n    }\n\n\n\nCredits\n-------\n\n* Henrik Ingo, henrik.ingo@avoinelama.fi, impress.js (plugin) integration\n* Heiko Richler, Aico.Richler@gmx.net, major changes in rev. 1.3\n* Lennart Regebro, regebro@gmail.com, main author of impressConsole\n* David Souther, davidsouther@gmail.com, author of the original notes.js\n\nMIT License\n"
  },
  {
    "path": "src/plugins/impressConsole/impressConsole.js",
    "content": "/**\n * Adds a presenter console to impress.js\n *\n * MIT Licensed, see license.txt.\n *\n * Copyright 2012, 2013, 2015 impress-console contributors (see README.txt)\n *\n * version: 1.3-dev\n *\n */\n\n// This file contains so much HTML, that we will just respectfully disagree about js\n/* jshint quotmark:single */\n/* global navigator, top, setInterval, clearInterval, document, window */\n\n( function( document, window ) {\n    'use strict';\n\n    // TODO: Move this to src/lib/util.js\n    var triggerEvent = function( el, eventName, detail ) {\n        var event = document.createEvent( 'CustomEvent' );\n        event.initCustomEvent( eventName, true, true, detail );\n        el.dispatchEvent( event );\n    };\n\n    // Create Language object depending on browsers language setting\n    var lang;\n    switch ( navigator.language ) {\n    case 'de':\n        lang = {\n            'noNotes': '<div class=\"noNotes\">Keine Notizen hierzu</div>',\n            'restart': 'Neustart',\n            'clickToOpen': 'Klicken um Sprecherkonsole zu öffnen',\n            'prev': 'zurück',\n            'next': 'weiter',\n            'loading': 'initalisiere',\n            'ready': 'Bereit',\n            'moving': 'in Bewegung',\n            'useAMPM': false\n        };\n        break;\n    case 'zh-CN':\n    case 'zh-cn':\n        lang = {\n            'noNotes': '<div class=\"noNotes\">当前帧没有备注</div>',\n            'restart': '重新开始',\n            'clickToOpen': '点击以打开演讲者控制界面',\n            'prev': '上一帧',\n            'next': '下一帧',\n            'loading': '加载中',\n            'ready': '就绪',\n            'moving': '移动中',\n            'useAMPM': false\n        };\n        break;\n    case 'en': // jshint ignore:line\n    default : // jshint ignore:line\n        lang = {\n            'noNotes': '<div class=\"noNotes\">No notes for this step</div>',\n            'restart': 'Restart',\n            'clickToOpen': 'Click to open speaker console',\n            'prev': 'Prev',\n            'next': 'Next',\n            'loading': 'Loading',\n            'ready': 'Ready',\n            'moving': 'Moving',\n            'useAMPM': false\n        };\n        break;\n    }\n\n    // Settings to set iframe in speaker console\n    const preViewDefaultFactor = 0.7;\n    const preViewMinimumFactor = 0.5;\n    const preViewGap    = 4;\n\n    // This is the default template for the speaker console window\n    const consoleTemplate = '<!DOCTYPE html>' +\n        '<html id=\"impressconsole\"><head>' +\n\n          // Order is important: If user provides a cssFile, those will win, because they're later\n          '{{cssStyle}}' +\n          '{{cssLink}}' +\n        '</head><body>' +\n        '<div id=\"console\">' +\n          '<div id=\"views\">' +\n            '<iframe id=\"slideView\" scrolling=\"no\"></iframe>' +\n            '<iframe id=\"preView\" scrolling=\"no\"></iframe>' +\n            '<div id=\"blocker\"></div>' +\n          '</div>' +\n          '<div id=\"notes\"></div>' +\n        '</div>' +\n        '<div id=\"controls\"> ' +\n          '<div id=\"prev\"><a  href=\"#\" onclick=\"impress().prev(); return false;\" />' +\n            '{{prev}}</a></div>' +\n          '<div id=\"next\"><a  href=\"#\" onclick=\"impress().next(); return false;\" />' +\n            '{{next}}</a></div>' +\n          '<div id=\"clock\">--:--</div>' +\n          '<div id=\"timer\" onclick=\"timerReset()\">00m 00s</div>' +\n          '<div id=\"status\">{{loading}}</div>' +\n        '</div>' +\n        '</body></html>';\n\n    // Default css location\n    var cssFileOldDefault = 'css/impressConsole.css';\n    var cssFile = undefined; // jshint ignore:line\n\n    // Css for styling iframs on the console\n    var cssFileIframeOldDefault = 'css/iframe.css';\n    var cssFileIframe = undefined; // jshint ignore:line\n\n    // All console windows, so that you can call impressConsole() repeatedly.\n    var allConsoles = {};\n\n    // Zero padding helper function:\n    var zeroPad = function( i ) {\n        return ( i < 10 ? '0' : '' ) + i;\n    };\n\n    // The console object\n    var impressConsole = window.impressConsole = function( rootId ) {\n\n        rootId = rootId || 'impress';\n\n        if ( allConsoles[ rootId ] ) {\n            return allConsoles[ rootId ];\n        }\n\n        // Root presentation elements\n        var root = document.getElementById( rootId );\n\n        var consoleWindow = null;\n\n        var nextStep = function() {\n            var classes = '';\n            var nextElement = document.querySelector( '.active' );\n\n            // Return to parents as long as there is no next sibling\n            while ( !nextElement.nextElementSibling && nextElement.parentNode ) {\n                nextElement = nextElement.parentNode;\n            }\n            nextElement = nextElement.nextElementSibling;\n            while ( nextElement ) {\n                classes = nextElement.attributes[ 'class' ];\n                if ( classes && classes.value.indexOf( 'step' ) !== -1 ) {\n                    consoleWindow.document.getElementById( 'blocker' ).innerHTML = lang.next;\n                    return nextElement;\n                }\n\n                if ( nextElement.firstElementChild ) { // First go into deep\n                    nextElement = nextElement.firstElementChild;\n                } else {\n\n                    // Go to next sibling or through parents until there is a next sibling\n                    while ( !nextElement.nextElementSibling && nextElement.parentNode ) {\n                        nextElement = nextElement.parentNode;\n                    }\n                    nextElement = nextElement.nextElementSibling;\n                }\n            }\n\n            // No next element. Pick the first\n            consoleWindow.document.getElementById( 'blocker' ).innerHTML = lang.restart;\n            return document.querySelector( '.step' );\n        };\n\n        // Sync the notes to the step\n        var onStepLeave = function() {\n            if ( consoleWindow ) {\n\n                // Set notes to next steps notes.\n                var newNotes = document.querySelector( '.active' ).querySelector( '.notes' );\n                if ( newNotes ) {\n                    newNotes = newNotes.innerHTML;\n                } else {\n                    newNotes = lang.noNotes;\n                }\n                consoleWindow.document.getElementById( 'notes' ).innerHTML = newNotes;\n\n                // Set the views\n                var baseURL = document.URL.substring( 0, document.URL.search( '#/' ) );\n                var slideSrc = baseURL + '#' + document.querySelector( '.active' ).id;\n                var preSrc = baseURL + '#' + nextStep().id;\n                var slideView = consoleWindow.document.getElementById( 'slideView' );\n\n                // Setting when already set causes glitches in Firefox, so check first:\n                if ( slideView.src !== slideSrc ) {\n                    slideView.src = slideSrc;\n                }\n                var preView = consoleWindow.document.getElementById( 'preView' );\n                if ( preView.src !== preSrc ) {\n                    preView.src = preSrc;\n                }\n\n                consoleWindow.document.getElementById( 'status' ).innerHTML =\n                    '<span class=\"moving\">' + lang.moving + '</span>';\n            }\n        };\n\n        // Sync the previews to the step\n        var onStepEnter = function() {\n            if ( consoleWindow ) {\n\n                // We do everything here again, because if you stopped the previos step to\n                // early, the onstepleave trigger is not called for that step, so\n                // we need this to sync things.\n                var newNotes = document.querySelector( '.active' ).querySelector( '.notes' );\n                if ( newNotes ) {\n                    newNotes = newNotes.innerHTML;\n                } else {\n                    newNotes = lang.noNotes;\n                }\n                var notes = consoleWindow.document.getElementById( 'notes' );\n                notes.innerHTML = newNotes;\n                notes.scrollTop = 0;\n\n                // Set the views\n                var baseURL = document.URL.substring( 0, document.URL.search( '#/' ) );\n                var slideSrc = baseURL + '#' + document.querySelector( '.active' ).id;\n                var preSrc = baseURL + '#' + nextStep().id;\n                var slideView = consoleWindow.document.getElementById( 'slideView' );\n\n                // Setting when already set causes glitches in Firefox, so check first:\n                if ( slideView.src !== slideSrc ) {\n                    slideView.src = slideSrc;\n                }\n                var preView = consoleWindow.document.getElementById( 'preView' );\n                if ( preView.src !== preSrc ) {\n                    preView.src = preSrc;\n                }\n\n                consoleWindow.document.getElementById( 'status' ).innerHTML =\n                    '<span  class=\"ready\">' + lang.ready + '</span>';\n            }\n        };\n\n        // Sync substeps\n        var onSubstep = function( event ) {\n            if ( consoleWindow ) {\n                if ( event.detail.reason === 'next' ) {\n                    onSubstepShow();\n                }\n                if ( event.detail.reason === 'prev' ) {\n                    onSubstepHide();\n                }\n            }\n        };\n\n        var onSubstepShow = function() {\n            var slideView = consoleWindow.document.getElementById( 'slideView' );\n            triggerEventInView( slideView, 'impress:substep:show' );\n        };\n\n        var onSubstepHide = function() {\n            var slideView = consoleWindow.document.getElementById( 'slideView' );\n            triggerEventInView( slideView, 'impress:substep:hide' );\n        };\n\n        var triggerEventInView = function( frame, eventName, detail ) {\n\n            // Note: Unfortunately Chrome does not allow createEvent on file:// URLs, so this won't\n            // work. This does work on Firefox, and should work if viewing the presentation on a\n            // http:// URL on Chrome.\n            var event = frame.contentDocument.createEvent( 'CustomEvent' );\n            event.initCustomEvent( eventName, true, true, detail );\n            frame.contentDocument.dispatchEvent( event );\n        };\n\n        var spaceHandler = function() {\n            var notes = consoleWindow.document.getElementById( 'notes' );\n            if ( notes.scrollTopMax - notes.scrollTop > 20 ) {\n               notes.scrollTop = notes.scrollTop + notes.clientHeight * 0.8;\n            } else {\n               window.impress().next();\n            }\n        };\n\n        var timerReset = function() {\n            consoleWindow.timerStart = new Date();\n        };\n\n        // Show a clock\n        var clockTick = function() {\n            var now = new Date();\n            var hours = now.getHours();\n            var minutes = now.getMinutes();\n            var seconds = now.getSeconds();\n            var ampm = '';\n\n            if ( lang.useAMPM ) {\n                ampm = ( hours < 12 ) ? 'AM' : 'PM';\n                hours = ( hours > 12 ) ? hours - 12 : hours;\n                hours = ( hours === 0 ) ? 12 : hours;\n            }\n\n            // Clock\n            var clockStr = zeroPad( hours ) + ':' + zeroPad( minutes ) + ':' + zeroPad( seconds ) +\n                           ' ' + ampm;\n            consoleWindow.document.getElementById( 'clock' ).firstChild.nodeValue = clockStr;\n\n            // Timer\n            seconds = Math.floor( ( now - consoleWindow.timerStart ) / 1000 );\n            minutes = Math.floor( seconds / 60 );\n            seconds = Math.floor( seconds % 60 );\n            consoleWindow.document.getElementById( 'timer' ).firstChild.nodeValue =\n                zeroPad( minutes ) + 'm ' + zeroPad( seconds ) + 's';\n\n            if ( !consoleWindow.initialized ) {\n\n                // Nudge the slide windows after load, or they will scrolled wrong on Firefox.\n                consoleWindow.document.getElementById( 'slideView' ).contentWindow.scrollTo( 0, 0 );\n                consoleWindow.document.getElementById( 'preView' ).contentWindow.scrollTo( 0, 0 );\n                consoleWindow.initialized = true;\n            }\n        };\n\n        var registerKeyEvent = function( keyCodes, handler, window ) {\n            if ( window === undefined ) {\n                window = consoleWindow;\n            }\n\n            // Prevent default keydown action when one of supported key is pressed\n            window.document.addEventListener( 'keydown', function( event ) {\n                if ( !event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey &&\n                     keyCodes.indexOf( event.keyCode ) !== -1 ) {\n                    event.preventDefault();\n                }\n            }, false );\n\n            // Trigger impress action on keyup\n            window.document.addEventListener( 'keyup', function( event ) {\n                if ( !event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey &&\n                     keyCodes.indexOf( event.keyCode ) !== -1 ) {\n                        handler();\n                        event.preventDefault();\n                }\n            }, false );\n        };\n\n        var consoleOnLoad = function() {\n                var slideView = consoleWindow.document.getElementById( 'slideView' );\n                var preView = consoleWindow.document.getElementById( 'preView' );\n\n                // Firefox:\n                slideView.contentDocument.body.classList.add( 'impress-console', 'slideView' );\n                preView.contentDocument.body.classList.add( 'impress-console', 'preView' );\n                if ( cssFileIframe !== undefined ) {\n                    slideView.contentDocument.head.insertAdjacentHTML(\n                        'beforeend',\n                        '<link rel=\"stylesheet\" type=\"text/css\" href=\"' + cssFileIframe + '\">'\n                    );\n                    preView.contentDocument.head.insertAdjacentHTML(\n                        'beforeend',\n                        '<link rel=\"stylesheet\" type=\"text/css\" href=\"' + cssFileIframe + '\">'\n                    );\n                }\n\n                // Chrome:\n                slideView.addEventListener( 'load', function() {\n                        slideView.contentDocument.body.classList.add( 'impress-console',\n                            'slideView' );\n                        if ( cssFileIframe !== undefined ) {\n                            slideView.contentDocument.head.insertAdjacentHTML(\n                                'beforeend',\n                                '<link rel=\"stylesheet\" type=\"text/css\" href=\"' +\n                                    cssFileIframe + '\">'\n                            );\n                        }\n                } );\n                preView.addEventListener( 'load', function() {\n                        preView.contentDocument.body.classList.add( 'impress-console', 'preView' );\n                        if ( cssFileIframe !== undefined ) {\n                            preView.contentDocument.head.insertAdjacentHTML(\n                                'beforeend',\n                                '<link rel=\"stylesheet\" type=\"text/css\" href=\"' +\n                                    cssFileIframe + '\">' );\n                        }\n                } );\n        };\n\n        var open = function() {\n            if ( top.isconsoleWindow ) {\n                return;\n            }\n\n            if ( consoleWindow && !consoleWindow.closed ) {\n                consoleWindow.focus();\n            } else {\n                consoleWindow = window.open( '', 'impressConsole' );\n\n                // If opening failes this may be because the browser prevents this from\n                // not (or less) interactive JavaScript...\n                if ( consoleWindow == null ) {\n\n                    // ... so I add a button to klick.\n                    // workaround on firefox\n                    var message = document.createElement( 'div' );\n                    message.id = 'impress-console-button';\n                    message.style.position = 'fixed';\n                    message.style.left = 0;\n                    message.style.top = 0;\n                    message.style.right = 0;\n                    message.style.bottom = 0;\n                    message.style.backgroundColor = 'rgba(255, 255, 255, 0.9)';\n                    var clickStr = 'var x = document.getElementById(\\'impress-console-button\\');' +\n                                     'x.parentNode.removeChild(x);' +\n                                     'var r = document.getElementById(\\'' + rootId + '\\');' +\n                                     'impress(\\'' + rootId +\n                                     '\\').lib.util.triggerEvent(r, \\'impress:console:open\\', {})';\n                    var styleStr = 'margin: 25vh 25vw;width:50vw;height:50vh;';\n                    message.innerHTML = '<button style=\"' + styleStr + '\" ' +\n                                                 'onclick=\"' + clickStr + '\">' +\n                                        lang.clickToOpen +\n                                        '</button>';\n                    document.body.appendChild( message );\n                    return;\n                }\n\n                var cssLink = '';\n                if ( cssFile !== undefined ) {\n                    cssLink = '<link rel=\"stylesheet\" type=\"text/css\" media=\"screen\" href=\"' +\n                              cssFile + '\">';\n                }\n\n                // This sets the window location to the main window location, so css can be loaded:\n                consoleWindow.document.open();\n\n                // Write the template:\n                consoleWindow.document.write(\n\n                    // CssStyleStr is lots of inline <style></style> defined at the end of this file\n                    consoleTemplate.replace( '{{cssStyle}}', cssStyleStr() )\n                                   .replace( '{{cssLink}}', cssLink )\n                                   .replace( /{{.*?}}/gi, function( x ) {\n                                       return lang[ x.substring( 2, x.length - 2 ) ]; }\n                                   )\n                );\n                consoleWindow.document.title = 'Speaker Console (' + document.title + ')';\n                consoleWindow.impress = window.impress;\n\n                // We set this flag so we can detect it later, to prevent infinite popups.\n                consoleWindow.isconsoleWindow = true;\n\n                // Set the onload function:\n                consoleWindow.onload = consoleOnLoad;\n\n                // Add clock tick\n                consoleWindow.timerStart = new Date();\n                consoleWindow.timerReset = timerReset;\n                consoleWindow.clockInterval = setInterval( allConsoles[ rootId ].clockTick, 1000 );\n\n                // Keyboard navigation handlers\n                // 33: pg up, 37: left, 38: up\n                registerKeyEvent( [ 33, 37, 38 ], window.impress().prev );\n\n                // 34: pg down, 39: right, 40: down\n                registerKeyEvent( [ 34, 39, 40 ], window.impress().next );\n\n                // 32: space\n                registerKeyEvent( [ 32 ], spaceHandler );\n\n                // 82: R\n                registerKeyEvent( [ 82 ], timerReset );\n\n                // Cleanup\n                consoleWindow.onbeforeunload = function() {\n\n                    // I don't know why onunload doesn't work here.\n                    clearInterval( consoleWindow.clockInterval );\n                };\n\n                // It will need a little nudge on Firefox, but only after loading:\n                onStepEnter();\n                consoleWindow.initialized = false;\n                consoleWindow.document.close();\n\n                //Catch any window resize to pass size on\n                window.onresize = resize;\n                consoleWindow.onresize = resize;\n\n                return consoleWindow;\n            }\n        };\n\n        var resize = function() {\n            var slideView = consoleWindow.document.getElementById( 'slideView' );\n            var preView = consoleWindow.document.getElementById( 'preView' );\n\n            // Get ratio of presentation\n            var ratio = window.innerHeight / window.innerWidth;\n\n            // Get size available for views\n            var views = consoleWindow.document.getElementById( 'views' );\n\n            // SlideView may have a border or some padding:\n            // asuming same border width on both direktions\n            var delta = slideView.offsetWidth - slideView.clientWidth;\n\n            // Set views\n            var slideViewWidth = ( views.clientWidth - delta );\n            var slideViewHeight = Math.floor( slideViewWidth * ratio );\n\n            var preViewTop = slideViewHeight + preViewGap;\n\n            var preViewWidth = Math.floor( slideViewWidth * preViewDefaultFactor );\n            var preViewHeight = Math.floor( slideViewHeight * preViewDefaultFactor );\n\n            // Shrink preview to fit into space available\n            if ( views.clientHeight - delta < preViewTop + preViewHeight ) {\n                preViewHeight = views.clientHeight - delta - preViewTop;\n                preViewWidth = Math.floor( preViewHeight / ratio );\n            }\n\n            // If preview is not high enough forget ratios!\n            if ( preViewWidth <= Math.floor( slideViewWidth * preViewMinimumFactor ) ) {\n                slideViewWidth = ( views.clientWidth - delta );\n                slideViewHeight = Math.floor( ( views.clientHeight - delta - preViewGap ) /\n                                             ( 1 + preViewMinimumFactor ) );\n\n                preViewTop = slideViewHeight + preViewGap;\n\n                preViewWidth = Math.floor( slideViewWidth * preViewMinimumFactor );\n                preViewHeight = views.clientHeight - delta - preViewTop;\n            }\n\n            // Set the calculated into styles\n            slideView.style.width = slideViewWidth + 'px';\n            slideView.style.height = slideViewHeight + 'px';\n\n            preView.style.top = preViewTop + 'px';\n\n            preView.style.width = preViewWidth + 'px';\n            preView.style.height = preViewHeight + 'px';\n        };\n\n        var _init = function( cssConsole, cssIframe ) {\n            if ( cssConsole !== undefined ) {\n                cssFile = cssConsole;\n            }\n\n            // You can also specify the css in the presentation root div:\n            // <div id=\"impress\" data-console-css=...\" data-console-css-iframe=\"...\">\n            else if ( root.dataset.consoleCss !== undefined ) {\n                cssFile = root.dataset.consoleCss;\n            }\n\n            if ( cssIframe !== undefined ) {\n                cssFileIframe = cssIframe;\n            } else if ( root.dataset.consoleCssIframe !== undefined ) {\n                cssFileIframe = root.dataset.consoleCssIframe;\n            }\n\n            // Register the event\n            root.addEventListener( 'impress:stepleave', onStepLeave );\n            root.addEventListener( 'impress:stepenter', onStepEnter );\n            root.addEventListener( 'impress:substep:stepleaveaborted', onSubstep );\n            root.addEventListener( 'impress:substep:show', onSubstepShow );\n            root.addEventListener( 'impress:substep:hide', onSubstepHide );\n\n            //When the window closes, clean up after ourselves.\n            window.onunload = function() {\n                if ( consoleWindow && !consoleWindow.closed ) {\n                    consoleWindow.close();\n                }\n            };\n\n            //Open speaker console when they press 'p'\n            registerKeyEvent( [ 80 ], open, window );\n\n            //Btw, you can also launch console automatically:\n            //<div id=\"impress\" data-console-autolaunch=\"true\">\n            if ( root.dataset.consoleAutolaunch === 'true' ) {\n                open();\n            }\n        };\n\n        var init = function( cssConsole, cssIframe ) {\n            if ( ( cssConsole === undefined || cssConsole === cssFileOldDefault ) &&\n                 ( cssIframe === undefined  || cssIframe === cssFileIframeOldDefault ) ) {\n                window.console.log( 'impressConsole().init() is deprecated. ' +\n                                   'impressConsole is now initialized automatically when you ' +\n                                   'call impress().init().' );\n            }\n            _init( cssConsole, cssIframe );\n        };\n\n        // New API for impress.js plugins is based on using events\n        root.addEventListener( 'impress:console:open', function() {\n            open();\n        } );\n\n        /**\n         * Register a key code to an event handler\n         *\n         * :param: event.detail.keyCodes    List of key codes\n         * :param: event.detail.handler     A function registered as the event handler\n         * :param: event.detail.window      The console window to register the keycode in\n         */\n        root.addEventListener( 'impress:console:registerKeyEvent', function( event ) {\n            registerKeyEvent( event.detail.keyCodes, event.detail.handler, event.detail.window );\n        } );\n\n        // Return the object\n        allConsoles[ rootId ] = { init: init, open: open, clockTick: clockTick,\n                               registerKeyEvent: registerKeyEvent, _init: _init };\n        return allConsoles[ rootId ];\n\n    };\n\n    // This initializes impressConsole automatically when initializing impress itself\n    document.addEventListener( 'impress:init', function( event ) {\n\n        // Note: impressConsole wants the id string, not the DOM element directly\n        impressConsole( event.target.id )._init();\n\n        // Add 'P' to the help popup\n        triggerEvent( document, 'impress:help:add',\n                        { command: 'P', text: 'Presenter console', row: 10 } );\n    } );\n\n    // Returns a string to be used inline as a css <style> element in the console window.\n    // Apologies for length, but hiding it here at the end to keep it away from rest of the code.\n    var cssStyleStr = function() {\n        return `<style>\n            #impressconsole body {\n                background-color: rgb(255, 255, 255);\n                padding: 0;\n                margin: 0;\n                font-family: verdana, arial, sans-serif;\n                font-size: 2vw;\n            }\n\n            #impressconsole div#console {\n                position: absolute;\n                top: 0.5vw;\n                left: 0.5vw;\n                right: 0.5vw;\n                bottom: 3vw;\n                margin: 0;\n            }\n\n            #impressconsole div#views, #impressconsole div#notes {\n                position: absolute;\n                top: 0;\n                bottom: 0;\n            }\n\n            #impressconsole div#views {\n                left: 0;\n                right: 50vw;\n                overflow: hidden;\n            }\n\n            #impressconsole div#blocker {\n                position: absolute;\n                right: 0;\n                bottom: 0;\n            }\n\n            #impressconsole div#notes {\n                left: 50vw;\n                right: 0;\n                overflow-x: hidden;\n                overflow-y: auto;\n                padding: 0.3ex;\n                background-color: rgb(255, 255, 255);\n                border: solid 1px rgb(120, 120, 120);\n            }\n\n            #impressconsole div#notes .noNotes {\n                color: rgb(200, 200, 200);\n            }\n\n            #impressconsole div#notes p {\n                margin-top: 0;\n            }\n\n            #impressconsole iframe {\n                position: absolute;\n                margin: 0;\n                padding: 0;\n                left: 0;\n                border: solid 1px rgb(120, 120, 120);\n            }\n\n            #impressconsole iframe#slideView {\n                top: 0;\n                width: 49vw;\n                height: 49vh;\n            }\n\n            #impressconsole iframe#preView {\n                opacity: 0.7;\n                top: 50vh;\n                width: 30vw;\n                height: 30vh;\n            }\n\n            #impressconsole div#controls {\n                margin: 0;\n                position: absolute;\n                bottom: 0.25vw;\n                left: 0.5vw;\n                right: 0.5vw;\n                height: 2.5vw;\n                background-color: rgb(255, 255, 255);\n                background-color: rgba(255, 255, 255, 0.6);\n            }\n\n            #impressconsole div#prev, div#next {\n            }\n\n            #impressconsole div#prev a, #impressconsole div#next a {\n                display: block;\n                border: solid 1px rgb(70, 70, 70);\n                border-radius: 0.5vw;\n                font-size: 1.5vw;\n                padding: 0.25vw;\n                text-decoration: none;\n                background-color: rgb(220, 220, 220);\n                color: rgb(0, 0, 0);\n            }\n\n            #impressconsole div#prev a:hover, #impressconsole div#next a:hover {\n                background-color: rgb(245, 245, 245);\n            }\n\n            #impressconsole div#prev {\n                float: left;\n            }\n\n            #impressconsole div#next {\n                float: right;\n            }\n\n            #impressconsole div#status {\n                margin-left: 2em;\n                margin-right: 2em;\n                text-align: center;\n                float: right;\n            }\n\n            #impressconsole div#clock {\n                margin-left: 2em;\n                margin-right: 2em;\n                text-align: center;\n                float: left;\n            }\n\n            #impressconsole div#timer {\n                margin-left: 2em;\n                margin-right: 2em;\n                text-align: center;\n                float: left;\n            }\n\n            #impressconsole span.moving {\n                color: rgb(255, 0, 0);\n            }\n\n            #impressconsole span.ready {\n                color: rgb(0, 128, 0);\n            }\n        </style>`;\n    };\n\n} )( document, window );\n"
  },
  {
    "path": "src/plugins/media/README.md",
    "content": "# Media\nThis plugin will do the following things:\n - Add a special class when playing (body.impress-media-video-playing and body.impress-media-video-playing) and pausing media (body.impress-media-video-paused and body.impress-media-audio-paused) (removing them when ending). This can be useful for example for darkening the background or fading out other elements while a video is playing. Only media at the current step are taken into account. All classes are removed when leaving a step.\n- Introduce the following new data attributes:\n    - data-media-autoplay=\"true\": Autostart media when entering its step.\n    - data-media-autostop=\"true\": Stop media (= pause and reset to start), when leaving its step.\n    - data-media-autopause=\"true\": Pause media but keep current time when leaving its step.\n\nWhen these attributes are added to a step they are inherited by all media on this step. Of course this setting can be overwritten by adding different attributes to inidvidual media.\n\nThe same rule applies when this attributes is added to the root element. Settings can be overwritten for individual steps and media.\nExamples:\n- data-media-autoplay=\"true\" data-media-autostop=\"true\": start media on enter, stop on leave, restart from beginning when re-entering the step.\n- data-media-autoplay=\"true\" data-media-autopause=\"true\": start media on enter, pause on leave, resume on re-enter\n- data-media-autoplay=\"true\" data-media-autostop=\"true\" data-media-autopause=\"true\": start media on enter, stop on leave (stop overwrites pause).\n- data-media-autoplay=\"true\" data-media-autopause=\"false\": let media start automatically when entering a step and let it play when leaving the step.\n- ```<div id=\"impress\" data-media-autoplay=\"true\"> ... <div class=\"step\" data-media-autoplay=\"false\">```\n All media is startet automatically on all steps except the one that has the data-media-autoplay=\"false\" attribute.\n- Pro tip: Use ```<audio onended=\"impress().next()\"> or <video onended=\"impress().next()\">``` to proceed to the next step automatically, when the end of the media is reached."
  },
  {
    "path": "src/plugins/media/media.js",
    "content": "/**\n * Media Plugin\n *\n * This plugin will do the following things:\n *\n *  - Add a special class when playing (body.impress-media-video-playing\n *    and body.impress-media-video-playing) and pausing media (body.impress-media-video-paused\n *    and body.impress-media-audio-paused) (removing them when ending).\n *    This can be useful for example for darkening the background or fading out other elements\n *    while a video is playing.\n *    Only media at the current step are taken into account. All classes are removed when leaving\n *    a step.\n *\n *  - Introduce the following new data attributes:\n *\n *    - data-media-autoplay=\"true\": Autostart media when entering its step.\n *    - data-media-autostop=\"true\": Stop media (= pause and reset to start), when leaving its\n *      step.\n *    - data-media-autopause=\"true\": Pause media but keep current time when leaving its step.\n *\n *    When these attributes are added to a step they are inherited by all media on this step.\n *    Of course this setting can be overwritten by adding different attributes to inidvidual\n *    media.\n *\n *    The same rule applies when this attributes is added to the root element. Settings can be\n *    overwritten for individual steps and media.\n *\n *    Examples:\n *    - data-media-autoplay=\"true\" data-media-autostop=\"true\": start media on enter, stop on\n *      leave, restart from beginning when re-entering the step.\n *\n *    - data-media-autoplay=\"true\" data-media-autopause=\"true\": start media on enter, pause on\n *      leave, resume on re-enter\n *\n *    - data-media-autoplay=\"true\" data-media-autostop=\"true\" data-media-autopause=\"true\": start\n *      media on enter, stop on leave (stop overwrites pause).\n *\n *    - data-media-autoplay=\"true\" data-media-autopause=\"false\": let media start automatically\n *      when entering a step and let it play when leaving the step.\n *\n *    - <div id=\"impress\" data-media-autoplay=\"true\"> ... <div class=\"step\"\n *      data-media-autoplay=\"false\">\n *      All media is startet automatically on all steps except the one that has the\n *      data-media-autoplay=\"false\" attribute.\n *\n *  - Pro tip: Use <audio onended=\"impress().next()\"> or <video onended=\"impress().next()\"> to\n *    proceed to the next step automatically, when the end of the media is reached.\n *\n *\n * Copyright 2018 Holger Teichert (@complanar)\n * Released under the MIT license.\n */\n/* global window, document */\n\n( function( document, window ) {\n    \"use strict\";\n    var root, api, gc, attributeTracker;\n\n    attributeTracker = [];\n\n    // Function names\n    var enhanceMediaNodes,\n        enhanceMedia,\n        removeMediaClasses,\n        onStepenterDetectImpressConsole,\n        onStepenter,\n        onStepleave,\n        onPlay,\n        onPause,\n        onEnded,\n        getMediaAttribute,\n        teardown;\n\n    document.addEventListener( \"impress:init\", function( event ) {\n        root = event.target;\n        api = event.detail.api;\n        gc = api.lib.gc;\n\n        enhanceMedia();\n\n        gc.pushCallback( teardown );\n    }, false );\n\n    teardown = function() {\n        var el, i;\n        removeMediaClasses();\n        for ( i = 0; i < attributeTracker.length; i += 1 ) {\n            el = attributeTracker[ i ];\n            el.node.removeAttribute( el.attr );\n        }\n        attributeTracker = [];\n    };\n\n    getMediaAttribute = function( attributeName, nodes ) {\n        var attrName, attrValue, i, node;\n        attrName = \"data-media-\" + attributeName;\n\n        // Look for attributes in all nodes\n        for ( i = 0; i < nodes.length; i += 1 ) {\n            node = nodes[ i ];\n\n            // First test, if the attribute exists, because some browsers may return\n            // an empty string for non-existing attributes - specs are not clear at that point\n            if ( node.hasAttribute( attrName ) ) {\n\n                // Attribute found, return their parsed boolean value, empty strings count as true\n                // to enable empty value booleans (common in html5 but not allowed in well formed\n                // xml).\n                attrValue = node.getAttribute( attrName );\n                if ( attrValue === \"\" || attrValue === \"true\" ) {\n                    return true;\n                } else {\n                    return false;\n                }\n            }\n\n            // No attribute found at current node, proceed with next round\n        }\n\n        // Last resort: no attribute found - return undefined to distiguish from false\n        return undefined;\n    };\n\n    onPlay = function( event ) {\n        var type = event.target.nodeName.toLowerCase();\n        document.body.classList.add( \"impress-media-\" + type + \"-playing\" );\n        document.body.classList.remove( \"impress-media-\" + type + \"-paused\" );\n    };\n\n    onPause = function( event ) {\n        var type = event.target.nodeName.toLowerCase();\n        document.body.classList.add( \"impress-media-\" + type + \"-paused\" );\n        document.body.classList.remove( \"impress-media-\" + type + \"-playing\" );\n    };\n\n    onEnded = function( event ) {\n        var type = event.target.nodeName.toLowerCase();\n        document.body.classList.remove( \"impress-media-\" + type + \"-playing\" );\n        document.body.classList.remove( \"impress-media-\" + type + \"-paused\" );\n    };\n\n    removeMediaClasses = function() {\n        var type, types;\n        types = [ \"video\", \"audio\" ];\n        for ( type in types ) {\n            document.body.classList.remove( \"impress-media-\" + types[ type ] + \"-playing\" );\n            document.body.classList.remove( \"impress-media-\" + types[ type ] + \"-paused\" );\n        }\n    };\n\n    enhanceMediaNodes = function() {\n        var i, id, media, mediaElement, type;\n\n        media = root.querySelectorAll( \"audio, video\" );\n        for ( i = 0; i < media.length; i += 1 ) {\n            type = media[ i ].nodeName.toLowerCase();\n\n            // Set an id to identify each media node - used e.g. for cross references by\n            // the consoleMedia plugin\n            mediaElement = media[ i ];\n            id = mediaElement.getAttribute( \"id\" );\n            if ( id === undefined || id === null ) {\n                mediaElement.setAttribute( \"id\", \"media-\" + type + \"-\" + i );\n                attributeTracker.push( { \"node\": mediaElement, \"attr\": \"id\" } );\n            }\n            gc.addEventListener( mediaElement, \"play\", onPlay );\n            gc.addEventListener( mediaElement, \"playing\", onPlay );\n            gc.addEventListener( mediaElement, \"pause\", onPause );\n            gc.addEventListener( mediaElement, \"ended\", onEnded );\n        }\n    };\n\n    enhanceMedia = function() {\n        var steps, stepElement, i;\n        enhanceMediaNodes();\n        steps = document.getElementsByClassName( \"step\" );\n        for ( i = 0; i < steps.length; i += 1 ) {\n            stepElement = steps[ i ];\n\n            gc.addEventListener( stepElement, \"impress:stepenter\", onStepenter );\n            gc.addEventListener( stepElement, \"impress:stepleave\", onStepleave );\n        }\n    };\n\n    onStepenterDetectImpressConsole = function() {\n        return {\n            \"preview\": ( window.frameElement !== null && window.frameElement.id === \"preView\" ),\n            \"slideView\": ( window.frameElement !== null && window.frameElement.id === \"slideView\" )\n        };\n    };\n\n    onStepenter = function( event ) {\n        var stepElement, media, mediaElement, i, onConsole, autoplay;\n        if ( ( !event ) || ( !event.target ) ) {\n            return;\n        }\n\n        stepElement = event.target;\n        removeMediaClasses();\n\n        media = stepElement.querySelectorAll( \"audio, video\" );\n        for ( i = 0; i < media.length; i += 1 ) {\n            mediaElement = media[ i ];\n\n            // Autoplay when (maybe inherited) autoplay setting is true,\n            // but only if not on preview of the next step in impressConsole\n            onConsole = onStepenterDetectImpressConsole();\n            autoplay = getMediaAttribute( \"autoplay\", [ mediaElement, stepElement, root ] );\n            if ( autoplay && !onConsole.preview ) {\n                if ( onConsole.slideView ) {\n                    mediaElement.muted = true;\n                }\n                mediaElement.play();\n            }\n        }\n    };\n\n    onStepleave = function( event ) {\n        var stepElement, media, i, mediaElement, autoplay, autopause, autostop;\n        if ( ( !event || !event.target ) ) {\n            return;\n        }\n\n        stepElement = event.target;\n        media = event.target.querySelectorAll( \"audio, video\" );\n        for ( i = 0; i < media.length; i += 1 ) {\n            mediaElement = media[ i ];\n\n            autoplay = getMediaAttribute( \"autoplay\", [ mediaElement, stepElement, root ] );\n            autopause = getMediaAttribute( \"autopause\", [ mediaElement, stepElement, root ] );\n            autostop = getMediaAttribute( \"autostop\",  [ mediaElement, stepElement, root ] );\n\n            // If both autostop and autopause are undefined, set it to the value of autoplay\n            if ( autostop === undefined && autopause === undefined ) {\n                autostop = autoplay;\n            }\n\n            if ( autopause || autostop ) {\n                mediaElement.pause();\n                if ( autostop ) {\n                    mediaElement.currentTime = 0;\n                }\n            }\n        }\n        removeMediaClasses();\n    };\n\n} )( document, window );\n"
  },
  {
    "path": "src/plugins/mobile/README.md",
    "content": "Mobile devices support\n======================\n\nPresentations with a lot of 3D effects and graphics can consume a lot of resources, especially on mobile devices.\nThis plugin provides some CSS classes that can be used to hide most of the slides, only showing the current, previous\nand next slide.\n\nIn particular, this plugin adds:\n\n`body.impress-mobile` class, if it detects running on a mobile OS.\n\n`div.prev` and `div.prev` to the adjacent steps to the current one. Note that the current slide is already identified\nby `present` and `active` CSS classes.\n\nExample CSS\n-----------\n\n        body.impress-mobile .step { \n            display:none;\n        }\n        body.impress-mobile .step.active,\n        body.impress-mobile .step.present,\n        body.impress-mobile .step.next,\n        body.impress-mobile .step.prev { \n            display:block; \n        }\n\nNote\n----\n\nThis plugin does not take into account redirects that could happen with skip, goto and other plugins. The active\nstep will of course always be correct, but \"non-linear\" transitions to anything else than the actual previous and next\nsteps will probably not look correct.\n\nAuthor\n------\n\nKurt Zenisek (@KZeni)\n"
  },
  {
    "path": "src/plugins/mobile/mobile.js",
    "content": "/**\n * Mobile devices support\n *\n * Allow presentation creators to hide all but 3 slides, to save resources, particularly on mobile\n * devices, using classes body.impress-mobile, .step.prev, .step.active and .step.next.\n *\n * Note: This plugin does not take into account possible redirections done with skip, goto etc\n * plugins. Basically it wouldn't work as intended in such cases, but the active step will at least\n * be correct.\n *\n * Adapted to a plugin from a submission by @Kzeni:\n * https://github.com/impress/impress.js/issues/333\n */\n/* global document, navigator */\n( function( document ) {\n    \"use strict\";\n\n    var getNextStep = function( el ) {\n        var steps = document.querySelectorAll( \".step\" );\n        for ( var i = 0; i < steps.length; i++ ) {\n            if ( steps[ i ] === el ) {\n                if ( i + 1 < steps.length ) {\n                    return steps[ i + 1 ];\n                } else {\n                    return steps[ 0 ];\n                }\n            }\n        }\n    };\n    var getPrevStep = function( el ) {\n        var steps = document.querySelectorAll( \".step\" );\n        for ( var i = steps.length - 1; i >= 0; i-- ) {\n            if ( steps[ i ] === el ) {\n                if ( i - 1 >= 0 ) {\n                    return steps[ i - 1 ];\n                } else {\n                    return steps[ steps.length - 1 ];\n                }\n            }\n        }\n    };\n\n    // Detect mobile browsers & add CSS class as appropriate.\n    document.addEventListener( \"impress:init\", function( event ) {\n        var body = document.body;\n        if ( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(\n                 navigator.userAgent\n             ) ) {\n            body.classList.add( \"impress-mobile\" );\n        }\n\n        // Unset all this on teardown\n        var api = event.detail.api;\n        api.lib.gc.pushCallback( function() {\n            document.body.classList.remove( \"impress-mobile\" );\n            var prev = document.getElementsByClassName( \"prev\" )[ 0 ];\n            var next = document.getElementsByClassName( \"next\" )[ 0 ];\n            if ( typeof prev !== \"undefined\" ) {\n                prev.classList.remove( \"prev\" );\n            }\n            if ( typeof next !== \"undefined\" ) {\n                next.classList.remove( \"next\" );\n            }\n        } );\n    } );\n\n    // Add prev and next classes to the siblings of the newly entered active step element\n    // Remove prev and next classes from their current step elements\n    // Note: As an exception we break namespacing rules, as these are useful general purpose\n    // classes. (Naming rules would require us to use css classes mobile-next and mobile-prev,\n    // based on plugin name.)\n    document.addEventListener( \"impress:stepenter\", function( event ) {\n\t      var oldprev = document.getElementsByClassName( \"prev\" )[ 0 ];\n\t      var oldnext = document.getElementsByClassName( \"next\" )[ 0 ];\n\n\t      var prev = getPrevStep( event.target );\n\t      prev.classList.add( \"prev\" );\n\t      var next = getNextStep( event.target );\n\t      next.classList.add( \"next\" );\n\n\t      if ( typeof oldprev !== \"undefined\" ) {\n\t\t      oldprev.classList.remove( \"prev\" );\n              }\n\t      if ( typeof oldnext !== \"undefined\" ) {\n\t\t      oldnext.classList.remove( \"next\" );\n              }\n    } );\n} )( document );\n\n"
  },
  {
    "path": "src/plugins/mouse-timeout/README.md",
    "content": "Mouse timeout plugin\n====================\n\nAfter 3 seconds of mouse inactivity, add the css class \n`body.impress-mouse-timeout`. On `mousemove`, `click` or `touch`, remove the\nclass.\n\nThe use case for this plugin is to use CSS to hide elements from the screen\nand only make them visible when the mouse is moved. Examples where this\nmight be used are: the toolbar from the toolbar plugin, and the mouse cursor\nitself.\n\nExample CSS\n------------\n\n    body.impress-mouse-timeout {\n        cursor: none;\n    }\n    body.impress-mouse-timeout div#impress-toolbar {\n        display: none;\n    }\n\n\nCopyright 2016 Henrik Ingo (@henrikingo)\nReleased under the MIT license.\n"
  },
  {
    "path": "src/plugins/mouse-timeout/mouse-timeout.js",
    "content": "/**\n * Mouse timeout plugin\n *\n * After 3 seconds of mouse inactivity, add the css class\n * `body.impress-mouse-timeout`. On `mousemove`, `click` or `touch`, remove the\n * class.\n *\n * The use case for this plugin is to use CSS to hide elements from the screen\n * and only make them visible when the mouse is moved. Examples where this\n * might be used are: the toolbar from the toolbar plugin, and the mouse cursor\n * itself.\n *\n * Example CSS:\n *\n *     body.impress-mouse-timeout {\n *         cursor: none;\n *     }\n *     body.impress-mouse-timeout div#impress-toolbar {\n *         display: none;\n *     }\n *\n *\n * Copyright 2016 Henrik Ingo (@henrikingo)\n * Released under the MIT license.\n */\n/* global window, document */\n( function( document, window ) {\n    \"use strict\";\n    var timeout = 3;\n    var timeoutHandle;\n\n    var hide = function() {\n\n        // Mouse is now inactive\n        document.body.classList.add( \"impress-mouse-timeout\" );\n    };\n\n    var show = function() {\n        if ( timeoutHandle ) {\n            window.clearTimeout( timeoutHandle );\n        }\n\n        // Mouse is now active\n        document.body.classList.remove( \"impress-mouse-timeout\" );\n\n        // Then set new timeout after which it is considered inactive again\n        timeoutHandle = window.setTimeout( hide, timeout * 1000 );\n    };\n\n    document.addEventListener( \"impress:init\", function( event ) {\n        var api = event.detail.api;\n        var gc = api.lib.gc;\n        gc.addEventListener( document, \"mousemove\", show );\n        gc.addEventListener( document, \"click\", show );\n        gc.addEventListener( document, \"touch\", show );\n\n        // Set first timeout\n        show();\n\n        // Unset all this on teardown\n        gc.pushCallback( function() {\n            window.clearTimeout( timeoutHandle );\n            document.body.classList.remove( \"impress-mouse-timeout\" );\n        } );\n    }, false );\n\n} )( document, window );\n"
  },
  {
    "path": "src/plugins/navigation/README.md",
    "content": "# Navigation\nAs you can see this part is separate from the impress.js core code.\nIt's because these navigation actions only need what impress.js provides with\nits simple API.\nThis plugin is what we call an _init plugin_. It's a simple kind of\nimpress.js plugin. When loaded, it starts listening to the `impress:init`\nevent. That event listener initializes the plugin functionality - in this\ncase we listen to some keypress and mouse events. The only dependencies on\ncore impress.js functionality is the `impress:init` method, as well as using\nthe public api `next(), prev(),` etc when keys are pressed.\nCopyright 2011-2012 Bartek Szopka (@bartaz)\nReleased under the MIT license.\n\n***Author:***\n\nBartek Szopka"
  },
  {
    "path": "src/plugins/navigation/navigation.js",
    "content": "/**\n * Navigation events plugin\n *\n * As you can see this part is separate from the impress.js core code.\n * It's because these navigation actions only need what impress.js provides with\n * its simple API.\n *\n * This plugin is what we call an _init plugin_. It's a simple kind of\n * impress.js plugin. When loaded, it starts listening to the `impress:init`\n * event. That event listener initializes the plugin functionality - in this\n * case we listen to some keypress and mouse events. The only dependencies on\n * core impress.js functionality is the `impress:init` method, as well as using\n * the public api `next(), prev(),` etc when keys are pressed.\n *\n * Copyright 2011-2012 Bartek Szopka (@bartaz)\n * Released under the MIT license.\n * ------------------------------------------------\n *  author:  Bartek Szopka\n *  version: 0.5.3\n *  url:     http://bartaz.github.com/impress.js/\n *  source:  http://github.com/bartaz/impress.js/\n *\n */\n/* global document */\n( function( document ) {\n    \"use strict\";\n\n    // Wait for impress.js to be initialized\n    document.addEventListener( \"impress:init\", function( event ) {\n\n        // Getting API from event data.\n        // So you don't event need to know what is the id of the root element\n        // or anything. `impress:init` event data gives you everything you\n        // need to control the presentation that was just initialized.\n        var api = event.detail.api;\n        var gc = api.lib.gc;\n        var util = api.lib.util;\n\n        // Supported keys are:\n        // [space] - quite common in presentation software to move forward\n        // [up] [right] / [down] [left] - again common and natural addition,\n        // [pgdown] / [pgup] - often triggered by remote controllers,\n        // [tab] - this one is quite controversial, but the reason it ended up on\n        //   this list is quite an interesting story... Remember that strange part\n        //   in the impress.js code where window is scrolled to 0,0 on every presentation\n        //   step, because sometimes browser scrolls viewport because of the focused element?\n        //   Well, the [tab] key by default navigates around focusable elements, so clicking\n        //   it very often caused scrolling to focused element and breaking impress.js\n        //   positioning. I didn't want to just prevent this default action, so I used [tab]\n        //   as another way to moving to next step... And yes, I know that for the sake of\n        //   consistency I should add [shift+tab] as opposite action...\n        var isNavigationEvent = function( event ) {\n\n            // Don't trigger navigation for example when user returns to browser window with ALT+TAB\n            if ( event.altKey || event.ctrlKey || event.metaKey ) {\n                return false;\n            }\n\n            // In the case of TAB, we force step navigation always, overriding the browser\n            // navigation between input elements, buttons and links.\n            if ( event.keyCode === 9 ) {\n                return true;\n            }\n\n            // With the sole exception of TAB, we also ignore keys pressed if shift is down.\n            if ( event.shiftKey ) {\n                return false;\n            }\n\n            if ( ( event.keyCode >= 32 && event.keyCode <= 34 ) ||\n                 ( event.keyCode >= 37 && event.keyCode <= 40 ) ) {\n                return true;\n            }\n        };\n\n        // KEYBOARD NAVIGATION HANDLERS\n\n        // Prevent default keydown action when one of supported key is pressed.\n        gc.addEventListener( document, \"keydown\", function( event ) {\n            if ( isNavigationEvent( event ) ) {\n                event.preventDefault();\n            }\n        }, false );\n\n        // Trigger impress action (next or prev) on keyup.\n        gc.addEventListener( document, \"keyup\", function( event ) {\n            if ( isNavigationEvent( event ) ) {\n                if ( event.shiftKey ) {\n                    switch ( event.keyCode ) {\n                        case 9: // Shift+tab\n                            api.prev();\n                            break;\n                    }\n                } else {\n                    switch ( event.keyCode ) {\n                        case 33: // Pg up\n                        case 37: // Left\n                        case 38: // Up\n                                 api.prev( event );\n                                 break;\n                        case 9:  // Tab\n                        case 32: // Space\n                        case 34: // Pg down\n                        case 39: // Right\n                        case 40: // Down\n                                 api.next( event );\n                                 break;\n                    }\n                }\n                event.preventDefault();\n            }\n        }, false );\n\n        // Delegated handler for clicking on the links to presentation steps\n        gc.addEventListener( document, \"click\", function( event ) {\n\n            // Event delegation with \"bubbling\"\n            // check if event target (or any of its parents is a link)\n            var target = event.target;\n            try {\n                while ( ( target.tagName !== \"A\" ) &&\n                        ( target !== document.documentElement ) ) {\n                    target = target.parentNode;\n                }\n\n                if ( target.tagName === \"A\" ) {\n                    var href = target.getAttribute( \"href\" );\n\n                    // If it's a link to presentation step, target this step\n                    if ( href && href[ 0 ] === \"#\" ) {\n                        target = document.getElementById( href.slice( 1 ) );\n                    }\n                }\n\n                if ( api.goto( target ) ) {\n                    event.stopImmediatePropagation();\n                    event.preventDefault();\n                }\n            }\n            catch ( err ) {\n\n                // For example, when clicking on the button to launch speaker console, the button\n                // is immediately deleted from the DOM. In this case target is a DOM element when\n                // we get it, but turns out to be null if you try to actually do anything with it.\n                if ( err instanceof TypeError &&\n                     err.message === \"target is null\" ) {\n                    return;\n                }\n                throw err;\n            }\n        }, false );\n\n        // Delegated handler for clicking on step elements\n        gc.addEventListener( document, \"click\", function( event ) {\n            var target = event.target;\n            try {\n\n                // Find closest step element that is not active\n                while ( !( target.classList.contains( \"step\" ) &&\n                        !target.classList.contains( \"active\" ) ) &&\n                        ( target !== document.documentElement ) ) {\n                    target = target.parentNode;\n                }\n\n                if ( api.goto( target ) ) {\n                    event.preventDefault();\n                }\n            }\n            catch ( err ) {\n\n                // For example, when clicking on the button to launch speaker console, the button\n                // is immediately deleted from the DOM. In this case target is a DOM element when\n                // we get it, but turns out to be null if you try to actually do anything with it.\n                if ( err instanceof TypeError &&\n                     err.message === \"target is null\" ) {\n                    return;\n                }\n                throw err;\n            }\n        }, false );\n\n        // Add a line to the help popup\n        util.triggerEvent( document, \"impress:help:add\", { command: \"Left &amp; Right\",\n                                                           text: \"Previous &amp; Next step\",\n                                                           row: 1 } );\n\n    }, false );\n\n} )( document );\n\n"
  },
  {
    "path": "src/plugins/navigation/navigation_tests.js",
    "content": "/*\n * Copyright 2016 Henrik Ingo (@henrikingo)\n *\n * Released under the MIT license. See LICENSE file.\n */\n/* global QUnit, loadIframe, initPresentation, document, window */\n\nQUnit.module( \"Navigation plugin\" );\n\nQUnit.test( \"Navigation Plugin\", function( assert ) {\n  window.console.log( \"Begin navigation plugin\" );\n  var done = assert.async();\n\n  loadIframe( \"test/core_tests_presentation.html\", assert, function() {\n    initPresentation( assert, function() {\n      var iframe = document.getElementById( \"presentation-iframe\" );\n      var iframeDoc = iframe.contentDocument;\n      var iframeWin = iframe.contentWindow;\n\n      var wait = 5; // Milliseconds\n\n      var step1 = iframeDoc.querySelector( \"div#step-1\" );\n      var step2 = iframeDoc.querySelector( \"div#step-2\" );\n      var step3 = iframeDoc.querySelector( \"div#step-3\" );\n      var step4 = iframeDoc.querySelector( \"div#fourth\" );\n      var root  = iframeDoc.querySelector( \"div#impress\" );\n\n      var i = 0;\n      var sequence = [ { left: step1,\n                         entered: step2,\n                         next: function() { return iframeWin.syn.type( \"bodyid\", \" \" ); },\n                         text: \"space (2->3)\" },\n                       { left: step2,\n                         entered: step3,\n                         next: function() { return iframeWin.syn.type( \"bodyid\", \"[right]\" ); },\n                         text: \"[right] (3->4)\" },\n                       { left: step3,\n                         entered: step4,\n                         next: function() { return iframeWin.syn.type( \"bodyid\", \"\\t\" ); },\n                         text: \"tab (4->1)\" },\n                       { left: step4,\n                         entered: step1,\n                         next: function() { return iframeWin.syn.type( \"bodyid\", \"[down]\" ); },\n                         text: \"[down] (1->2)\" },\n                       { left: step1,\n                         entered: step2,\n                         next: function() { return iframeWin.syn.type( \"bodyid\", \"[page-down]\" ); },\n                         text: \"[page-down] (2->3)\" },\n                       { left: step2,\n                         entered: step3,\n                         next: function() { return iframeWin.syn.type( \"bodyid\", \"[page-up]\" ); },\n                         text: \"[page-up] (3->2)\" },\n                       { left: step3,\n                         entered: step2,\n                         next: function() { return iframeWin.syn.type( \"bodyid\", \"[left]\" ); },\n                         text: \"[left] (2->1)\" },\n                       { left: step2,\n                         entered: step1,\n                         next: function() { return iframeWin.syn.type( \"bodyid\", \"[up]\" ); },\n                         text: \"[up] (1->4)\" },\n                       { left: step1,\n                         entered: step4,\n                         next: function() { return iframeWin.syn.click( \"step-2\", {} ); },\n                         text: \"click on 2 (4->2)\" },\n                       { left: step4,\n                         entered: step2,\n                         next: function() { return iframeWin.syn.click( \"linktofourth\", {} ); },\n                         text: \"click on link with href to id=fourth (2->4)\" },\n                       { left: step2,\n                         entered: step4,\n                         next: function() { return iframeWin.impress().goto( 0 ); },\n                         text: \"Return to first step with goto(0).\" },\n                       { left: step4,\n                         entered: step1,\n                         next: false }\n      ];\n\n      var readyCount = 0;\n      var readyForNext = function() {\n        readyCount++;\n        if ( readyCount % 2 === 0 ) {\n          if ( sequence[ i ].next ) {\n            assert.ok( sequence[ i ].next(), sequence[ i ].text );\n            i++;\n          } else {\n            window.console.log( \"End navigation plugin\" );\n            done();\n          }\n        }\n      };\n\n      // Things to check on impress:stepenter event -----------------------------//\n      var assertStepEnter = function( event ) {\n        assert.equal( event.target, sequence[ i ].entered,\n                      event.target.id + \" triggered impress:stepenter event.\" );\n        readyForNext();\n      };\n\n      var assertStepEnterWrapper = function( event ) {\n        window.setTimeout( function() { assertStepEnter( event ); }, wait );\n      };\n      root.addEventListener( \"impress:stepenter\", assertStepEnterWrapper );\n\n      // Things to check on impress:stepleave event -----------------------------//\n      var assertStepLeave = function( event ) {\n        assert.equal( event.target, sequence[ i ].left,\n                      event.target.id + \" triggered impress:stepleave event.\" );\n        readyForNext();\n      };\n\n      var assertStepLeaveWrapper = function( event ) {\n        window.setTimeout( function() { assertStepLeave( event ); }, wait );\n      };\n      root.addEventListener( \"impress:stepleave\", assertStepLeaveWrapper );\n\n      assert.ok( iframeWin.impress().next(), \"next() called and returns ok (1->2)\" );\n    } ); // InitPresentation()\n  } ); // LoadIframe()\n} );\n\nQUnit.test( \"Navigation Plugin - No-op tests\", function( assert ) {\n  window.console.log( \"Begin navigation no-op\" );\n  var done = assert.async();\n\n  loadIframe( \"test/core_tests_presentation.html\", assert, function() {\n    initPresentation( assert, function() {\n      var iframe = document.getElementById( \"presentation-iframe\" );\n      var iframeDoc = iframe.contentDocument;\n      var iframeWin = iframe.contentWindow;\n\n      var wait = 5; // Milliseconds\n\n      var root  = iframeDoc.querySelector( \"div#impress\" );\n\n      // This should never happen -----------------------------//\n      var assertStepEnter = function( event ) {\n        assert.ok( false,\n                   event.target.id + \" triggered impress:stepenter event.\" );\n      };\n\n      var assertStepEnterWrapper = function( event ) {\n        window.setTimeout( function() { assertStepEnter( event ); }, wait );\n      };\n      root.addEventListener( \"impress:stepenter\", assertStepEnterWrapper );\n\n      // This should never happen -----------------------------//\n      var assertStepLeave = function( event ) {\n        assert.ok( false,\n                   event.target.id + \" triggered impress:stepleave event.\" );\n      };\n\n      var assertStepLeaveWrapper = function( event ) {\n        window.setTimeout( function() { assertStepLeave( event ); }, wait );\n      };\n      root.addEventListener( \"impress:stepleave\", assertStepLeaveWrapper );\n\n      // These are no-op actions, we're already in step-1 -----------------------//\n      //assert.ok( iframeWin.syn.click( \"step-1\", {} ),\n      //           \"Click on step that is currently active, should do nothing.\" );\n      assert.ok( iframeWin.syn.click( \"linktofirst\", {} ),\n                 \"Click on link pointing to step that is currently active, should do nothing.\" );\n\n      // After delay, if no event triggers are called. We're done.\n      window.setTimeout( function() {\n          window.console.log( \"End navigation no-op\" ); done();\n      }, 3000 );\n    } ); // InitPresentation()\n  } ); // LoadIframe()\n} );\n"
  },
  {
    "path": "src/plugins/navigation-ui/README.md",
    "content": "Navigation UI plugin\n====================\n\nThis plugin provides UI elements \"back\", \"forward\" and a list to select\na specific slide number.\n\nElement attribut title is used for select option content if available, it uses element id if no title is provided.\n\nThe navigation controls are visible if the toolbar plugin is enabled. To add the toolbar to your\npresentations, [see toolbar plugin README](../toolbar/README.md).\n\nAuthor\n------\n\nHenrik Ingo (@henrikingo), 2016\n"
  },
  {
    "path": "src/plugins/navigation-ui/navigation-ui.js",
    "content": "/**\n * Navigation UI plugin\n *\n * This plugin provides UI elements \"back\", \"forward\" and a list to select\n * a specific slide number.\n *\n * The navigation controls are added to the toolbar plugin via DOM events. User must enable the\n * toolbar in a presentation to have them visible.\n *\n * Copyright 2016 Henrik Ingo (@henrikingo)\n * Released under the MIT license.\n */\n\n// This file contains so much HTML, that we will just respectfully disagree about js\n/* jshint quotmark:single */\n/* global document */\n\n( function( document ) {\n    'use strict';\n    var toolbar;\n    var api;\n    var root;\n    var steps;\n    var hideSteps = [];\n    var prev;\n    var select;\n    var next;\n\n    var triggerEvent = function( el, eventName, detail ) {\n        var event = document.createEvent( 'CustomEvent' );\n        event.initCustomEvent( eventName, true, true, detail );\n        el.dispatchEvent( event );\n    };\n\n    var makeDomElement = function( html ) {\n        var tempDiv = document.createElement( 'div' );\n        tempDiv.innerHTML = html;\n        return tempDiv.firstChild;\n    };\n\n    var getStepTitle = function( step ) {\n\n        // Max length for title.\n        // Line longer than this will be cutted.\n        const MAX_TITLE_LEN = 40;\n\n        if ( step.title ) {\n            return step.title;\n        }\n\n        // Neither title nor id is defined\n        if ( step.id.startsWith( 'step-' ) ) {\n            for ( var line of step.innerText.split( '\\n' ) ) {\n                line = line.trim( );\n                if ( line.length > 0 ) {\n                    if ( line.length <= MAX_TITLE_LEN ) {\n                        return line;\n                    } else {\n                        return line.slice( 0, MAX_TITLE_LEN - 3 ) + '...';\n                    }\n                }\n            }\n        }\n\n        return step.id;\n    };\n\n    var selectOptionsHtml = function() {\n        var options = '';\n        for ( var i = 0; i < steps.length; i++ ) {\n\n            // Omit steps that are listed as hidden from select widget\n            if ( hideSteps.indexOf( steps[ i ] ) < 0 ) {\n                options = options + '<option value=\"' + steps[ i ].id + '\">' + // jshint ignore:line\n\t\t\t\t\t\t\t(\n\t\t\t\t\t\t\t\tgetStepTitle( steps[ i ] )\n\t\t\t\t\t\t\t) + '</option>' + '\\n';\n            }\n        }\n        return options;\n    };\n\n    var addNavigationControls = function( event ) {\n        api = event.detail.api;\n        var gc = api.lib.gc;\n        root = event.target;\n        steps = root.querySelectorAll( '.step' );\n\n        var prevHtml   = '<button id=\"impress-navigation-ui-prev\" title=\"Previous\" ' +\n                         'class=\"impress-navigation-ui\">&lt;</button>';\n        var selectHtml = '<select id=\"impress-navigation-ui-select\" title=\"Go to\" ' +\n                         'class=\"impress-navigation-ui\">' + '\\n' +\n                           selectOptionsHtml() +\n                           '</select>';\n        var nextHtml   = '<button id=\"impress-navigation-ui-next\" title=\"Next\" ' +\n                         'class=\"impress-navigation-ui\">&gt;</button>';\n\n        prev = makeDomElement( prevHtml );\n        prev.addEventListener( 'click',\n            function() {\n                api.prev();\n        } );\n        select = makeDomElement( selectHtml );\n        select.addEventListener( 'change',\n            function( event ) {\n                api.goto( event.target.value );\n        } );\n        gc.addEventListener( root, 'impress:steprefresh', function( event ) {\n\n            // As impress.js core now allows to dynamically edit the steps, including adding,\n            // removing, and reordering steps, we need to requery and redraw the select list on\n            // every stepenter event.\n            steps = root.querySelectorAll( '.step' );\n            select.innerHTML = '\\n' + selectOptionsHtml();\n\n            // Make sure the list always shows the step we're actually on, even if it wasn't\n            // selected from the list\n            select.value = event.target.id;\n        } );\n        next = makeDomElement( nextHtml );\n        next.addEventListener( 'click',\n            function() {\n                api.next();\n        } );\n\n        triggerEvent( toolbar, 'impress:toolbar:appendChild', { group: 0, element: prev } );\n        triggerEvent( toolbar, 'impress:toolbar:appendChild', { group: 0, element: select } );\n        triggerEvent( toolbar, 'impress:toolbar:appendChild', { group: 0, element: next } );\n\n    };\n\n    // API for not listing given step in the select widget.\n    // For example, if you set class=\"skip\" on some element, you may not want it to show up in the\n    // list either. Otoh we cannot assume that, or anything else, so steps that user wants omitted\n    // must be specifically added with this API call.\n    document.addEventListener( 'impress:navigation-ui:hideStep', function( event ) {\n        hideSteps.push( event.target );\n        if ( select ) {\n            select.innerHTML = selectOptionsHtml();\n        }\n    }, false );\n\n    // Wait for impress.js to be initialized\n    document.addEventListener( 'impress:init', function( event ) {\n        toolbar = document.querySelector( '#impress-toolbar' );\n        if ( toolbar ) {\n            addNavigationControls( event );\n        }\n    }, false );\n\n} )( document );\n\n"
  },
  {
    "path": "src/plugins/progress/README.md",
    "content": "Progress plugin\n===============\n\nProgressbar and pagexounter for impress.js presentations\n\nUsage\n-----\n\nAdd a div for progressbar and/or progress as you can see it here:\n\n### HTML\n\n\t  <div class=\"impress-progressbar\"><div></div></div>\n\t  <div class=\"impress-progress\"></div>\n\n### Sample CSS\n\n    .impress-progressbar {\n      position: absolute;\n      right: 318px;\n      bottom: 1px;\n      left: 118px;\n      border-radius: 7px;\n      border: 2px solid rgba(100, 100, 100, 0.2);\n    }\n    .impress-progressbar DIV {\n      width: 0;\n      height: 2px;\n      border-radius: 5px;\n      background: rgba(75, 75, 75, 0.4);\n      transition: width 1s linear;\n    }\n    .impress-progress {\n      position: absolute;\n      left: 59px;\n      bottom: 1px;\n      text-align: left;\n      opacity: 0.6;\n    }\n\nFeel free to change the style of your progressbar as you like by editing the CSS file.\n\nAuthor\n------\n\nCopyright 2014: Matthias Bilger (@m42e)\n"
  },
  {
    "path": "src/plugins/progress/progress.js",
    "content": "/* global document */\n( function( document ) {\n    \"use strict\";\n    var root;\n    var stepids = [];\n\n    // Get stepids from the steps under impress root\n    var getSteps = function() {\n        stepids = [];\n        var steps = root.querySelectorAll( \".step\" );\n        for ( var i = 0; i < steps.length; i++ )\n        {\n          stepids[ i + 1 ] = steps[ i ].id;\n        }\n        };\n\n    // Wait for impress.js to be initialized\n    document.addEventListener( \"impress:init\", function( event ) {\n            root = event.target;\n        getSteps();\n        var gc = event.detail.api.lib.gc;\n        gc.pushCallback( function() {\n            stepids = [];\n            if ( progressbar ) {\n                progressbar.style.width = \"\";\n                        }\n            if ( progress ) {\n                progress.innerHTML = \"\";\n                        }\n        } );\n    } );\n\n    var progressbar = document.querySelector( \"div.impress-progressbar div\" );\n    var progress = document.querySelector( \"div.impress-progress\" );\n\n    if ( null !== progressbar || null !== progress ) {\n        document.addEventListener( \"impress:stepleave\", function( event ) {\n            updateProgressbar( event.detail.next.id );\n        } );\n\n        document.addEventListener( \"impress:steprefresh\", function( event ) {\n            getSteps();\n            updateProgressbar( event.target.id );\n        } );\n\n    }\n\n    function updateProgressbar( slideId ) {\n        var slideNumber = stepids.indexOf( slideId );\n        if ( null !== progressbar ) {\n                        var width = 100 / ( stepids.length - 1 ) * ( slideNumber );\n            progressbar.style.width = width.toFixed( 2 ) + \"%\";\n        }\n        if ( null !== progress ) {\n            progress.innerHTML = slideNumber + \"/\" + ( stepids.length - 1 );\n        }\n    }\n} )( document );\n"
  },
  {
    "path": "src/plugins/rel/README.md",
    "content": "Relative Positioning Plugin\n===========================\n\nThis plugin provides support for defining the coordinates of a step relative\nto previous steps. This is often more convenient when creating presentations,\nsince as you add, remove or move steps, you may not need to edit the positions\nas much as is the case with the absolute coordinates supported by impress.js\ncore.\n\nExample:\n\n    <!-- Position step 1000 px to the right and 500 px up from the previous step. -->\n    <div class=\"step\" data-rel-x=\"1000\" data-rel-y=\"500\">\n        \n    <!-- Position step 1000 px to the left and 750 px up from the step with id \"title\". -->\n    <div class=\"step\" data-rel-x=\"-1000\" data-rel-y=\"750\" data-rel-to=\"title\">\n\nFollowing html attributes are supported for step elements:\n\n    data-rel-x\n    data-rel-y\n    data-rel-z\n    data-rel-to\n\n    data-rel-rotate-x\n    data-rel-rotate-y\n    data-rel-rotate-z\n\n    data-rel-position\n    data-rel-reset\n\nNon-zero values are also inherited from the previous step. This makes it easy to \ncreate a boring presentation where each slide shifts for example 1000px down \nfrom the previous.\n\nThe above relative values are ignored, or set to zero, if the corresponding \nabsolute value (`data-x` etc...) is set. Note that this also has the effect of\nresetting the inheritance functionality.\n\nIn addition to plain numbers, which are pixel values, it is also possible to\ndefine relative positions as a multiple of screen height and width, using\na unit of \"h\" and \"w\", respectively, appended to the number.\n\nExample:\n\n    <div class=\"step\" data-rel-x=\"1.5w\" data-rel-y=\"1.5h\">\n\nNote that referencing a special step with the `data-rel-to` attribute is *limited to previous steps* to avoid the possibility of circular or offending positioning.\nIf you need a reference to a step that is shown later make use of the goto plugin:\n\n\n    <div id=\"shown-first\" class=\"step\" data-goto-next=\"shown-earlier\">\n    <div id=\"shown-later\" class=\"step\" data-goto-prev=\"shown-earlier\" data-goto-next=\"shown-last\">\n    <div id=\"shown-earlier\" class=\"step\" data-rel-to=\"shown-later\" data-rel-x=\"1000\" data-rel-y=\"500\" data-goto-prev=\"shown-first\" data-goto-next=\"shown-later\">\n    <div id=\"shown-last\" class=\"step\" data-goto-prev=\"shown-later\">\n\nRelative positioning\n--------------------\n\nAll `data-rel-x`/`y`/`z` is used world coordinate by default. So the value should be alternated according to the rotation state of previous slides.\n\nTo easy relative positioning, the `data-rel-position` attribute is introduced.\n\n`data-rel-position` has a default value of \"absolute\", which works like this attribute is not introduced.\n\nWhen `data-rel-position=\"relative\"`, everything changed. The rotation of previous is no need to be considered, you can set all the `data-rel-` attributes as if the previous has no rotation. This plugin will calculate the acual position according to the position and rotation of the previous slide and the relative settings. This make it possible to split a presentation into parts, construct each parts standalone without consider the final position, and then assemble them together.\n\nFor example, if you want the slide slided in from the right hand side, all you need is `data-rel-x=\"1000\"`, no matter the rotation of the previous slide. If the previous slide has `data-rotate-z=\"90\"`, the actual attribute of this slide will work like `data-rel-y=\"1000\"`.\n\nNot only relative positions, the relative rotations can be used while `data-rel-position=\"relative\"`.\n\nFor example, `data-rel-rotate-y=\"45\"` will make the slide has an angle of 45 degree to the previous slide. It make it easy to build a circle and do other complicated positioning.\n\nIf not set, the `data-rel-position` attribute will be inherited from previous slide. So we only need to set it at the first slide, then all done.\n\nTo avoid the boring need to set most `data-rel-*` to zero, but set only one or two ones, `data-rel-reset` attribute can be used: \n\n- `data-rel-reset` equals to: `data-rel-x=\"0\" data-rel-y=\"0\" data-rel-z=\"0\" data-rel-rotate-x=\"0\" data-rel-rotate-y=\"0\" data-rel-rotate-z=\"0\"`\n- `data-rel-reset=\"all\"` works like `data-rel-reset`, in additions `data-rotate-x=\"0\" data-rotate-y=\"0\" data-rotate-z=\"0\"`\n\nWhen `data-rel-position=\"relative\"` and `data-rel-to` is specified, `data-rotate-*` and `data-rel-*` will be inherited from specified slide too.\n\nIMPORTANT: Incompatible change\n------------------------------\n\nEnabling / adding this plugin has a small incompatible side effect on default values.\n\nPrior to this plugin, a missing data-x/y/z attribute would be assigned the default value of 0.\nBut when using a version of impress.js with this plugin enabled, a missing data-x/y/z attribute\nwill inherit the value from the previous step. (The first step will inherit the default value of 0.)\n\nFor example, if you have an old presentation with the following 3 steps, they would be positioned\ndifferently when using a version of impress.js that includes this plugin:\n\n    <div class=\"step\" data-x=\"100\" data-y=\"100\" data-z=\"100\"></div>\n    <div class=\"step\" data-x=\"100\" data-y=\"100\"></div>\n    <div class=\"step\" data-x=\"100\" data-y=\"100\"></div>\n\nTo get the same rendering now, you need to add an explicit `data-z=\"0\"` to the second step:\n\n    <div class=\"step\" data-x=\"100\" data-y=\"100\" data-z=\"100\"></div>\n    <div class=\"step\" data-x=\"100\" data-y=\"100\" data-z=\"0\"></div>\n    <div class=\"step\" data-x=\"100\" data-y=\"100\"></div>\n\nNote that the latter code will render correctly also in old versions of impress.js.\n\nIf you have an old presentation that doesn't use relative positioning, and for some reason you\ncannot or don't want to add the explicit 0 values where needed, your last resort is to simply\nremove the `rel.js` plugin completely. You can either:\n\n* Remove `rel.js` from [/build.js](../../../build.js) and recompile `impress.js` with: `npm build`\n* Just open [/js/impress.js] in an editor and delete the `rel.js` code.\n* Or, just uncomment the following single line, which is the last line of the plugin:\n\n        impress.addPreInitPlugin( rel );\n\n\nAbout Pre-Init Plugins\n----------------------\n\nThis plugin is a *pre-init plugin*. It is called synchronously from impress.js\ncore at the beginning of `impress().init()`. This allows it to process its own\ndata attributes first, and possibly alter the data-x, data-y and data-z attributes\nthat will then be processed by `impress().init()`.\n\n(Another name for this kind of plugin might be called a *filter plugin*, but\n*pre-init plugin* is more generic, as a plugin might do whatever it wants in\nthe pre-init stage.)\n\n\nAuthor\n------\n\nHenrik Ingo (@henrikingo), 2016\n"
  },
  {
    "path": "src/plugins/rel/rel.js",
    "content": "/**\n * Relative Positioning Plugin\n *\n * This plugin provides support for defining the coordinates of a step relative\n * to the previous step. This is often more convenient when creating presentations,\n * since as you add, remove or move steps, you may not need to edit the positions\n * as much as is the case with the absolute coordinates supported by impress.js\n * core.\n *\n * Example:\n *\n *         <!-- Position step 1000 px to the right and 500 px up from the previous step. -->\n *         <div class=\"step\" data-rel-x=\"1000\" data-rel-y=\"500\">\n *\n * Following html attributes are supported for step elements:\n *\n *     data-rel-x\n *     data-rel-y\n *     data-rel-z\n *\n * These values are also inherited from the previous step. This makes it easy to\n * create a boring presentation where each slide shifts for example 1000px down\n * from the previous.\n *\n * In addition to plain numbers, which are pixel values, it is also possible to\n * define relative positions as a multiple of screen height and width, using\n * a unit of \"h\" and \"w\", respectively, appended to the number.\n *\n * Example:\n *\n *        <div class=\"step\" data-rel-x=\"1.5w\" data-rel-y=\"1.5h\">\n *\n * This plugin is a *pre-init plugin*. It is called synchronously from impress.js\n * core at the beginning of `impress().init()`. This allows it to process its own\n * data attributes first, and possibly alter the data-x, data-y and data-z attributes\n * that will then be processed by `impress().init()`.\n *\n * (Another name for this kind of plugin might be called a *filter plugin*, but\n * *pre-init plugin* is more generic, as a plugin might do whatever it wants in\n * the pre-init stage.)\n *\n * Copyright 2016 Henrik Ingo (@henrikingo)\n * Released under the MIT license.\n */\n\n/* global document, window */\n\n( function( document, window ) {\n    \"use strict\";\n\n    var api;\n    var startingState = {};\n\n    var toNumber;\n    var toNumberAdvanced;\n\n    var computeRelativePositions = function( el, prev ) {\n        var data = el.dataset;\n\n        if ( !prev ) {\n\n            // For the first step, inherit these defaults\n            prev = {\n                x:0, y:0, z:0,\n                rotate: { x:0, y:0, z:0, order:\"xyz\" },\n                relative: {\n                    position: \"absolute\",\n                    x:0, y:0, z:0,\n                    rotate: { x:0, y:0, z:0, order:\"xyz\" }\n                }\n            };\n        }\n\n        var ref = prev;\n        if ( data.relTo ) {\n\n            ref = document.getElementById( data.relTo );\n            if ( ref ) {\n\n                // Test, if it is a previous step that already has some assigned position data\n                if ( el.compareDocumentPosition( ref ) & Node.DOCUMENT_POSITION_PRECEDING ) {\n                    prev.x = toNumberAdvanced( ref.getAttribute( \"data-x\" ) );\n                    prev.y = toNumberAdvanced( ref.getAttribute( \"data-y\" ) );\n                    prev.z = toNumberAdvanced( ref.getAttribute( \"data-z\" ) );\n\n                    var prevPosition = ref.getAttribute( \"data-rel-position\" ) || \"absolute\";\n\n                    if ( prevPosition !== \"relative\" ) {\n\n                        // For compatibility with the old behavior, doesn't inherit otherthings,\n                        // just like a reset.\n                        prev.rotate = { x:0, y:0, z:0, order: \"xyz\" };\n                        prev.relative = {\n                            position: \"absolute\",\n                            x:0, y:0, z:0,\n                            rotate: { x:0, y:0, z:0, order:\"xyz\" }\n                        };\n                    } else {\n\n                        // For data-rel-position=\"relative\", inherit all\n                        prev.rotate.y = toNumber( ref.getAttribute( \"data-rotate-y\" ) );\n                        prev.rotate.x = toNumber( ref.getAttribute( \"data-rotate-x\" ) );\n                        prev.rotate.z = toNumber(\n                            ref.getAttribute( \"data-rotate-z\" ) ||\n                            ref.getAttribute( \"data-rotate\" ) );\n\n                        // We also inherit relatives from relTo slide\n                        prev.relative = {\n                            position: prevPosition,\n                            x: toNumberAdvanced( ref.getAttribute( \"data-rel-x\" ), 0 ),\n                            y: toNumberAdvanced( ref.getAttribute( \"data-rel-y\" ), 0 ),\n                            z: toNumberAdvanced( ref.getAttribute( \"data-rel-z\" ), 0 ),\n                            rotate: {\n                                x: toNumberAdvanced( ref.getAttribute( \"data-rel-rotate-x\" ), 0 ),\n                                y: toNumberAdvanced( ref.getAttribute( \"data-rel-rotate-y\" ), 0 ),\n                                z: toNumberAdvanced( ref.getAttribute( \"data-rel-rotate-z\" ), 0 ),\n                                order: ( ref.getAttribute( \"data-rel-rotate-order\" ) ||  \"xyz\" )\n                            }\n                        };\n                    }\n                } else {\n                    window.console.error(\n                        \"impress.js rel plugin: Step \\\"\" + data.relTo + \"\\\" is not defined \" +\n                        \"*before* the current step. Referencing is limited to previously defined \" +\n                        \"steps. Please check your markup. Ignoring data-rel-to attribute of \" +\n                        \"this step. Have a look at the documentation for how to create relative \" +\n                        \"positioning to later shown steps with the help of the goto plugin.\"\n                    );\n                }\n            } else {\n\n                // Step not found\n                window.console.warn(\n                    \"impress.js rel plugin: \\\"\" + data.relTo + \"\\\" is not a valid step in this \" +\n                    \"impress.js presentation. Please check your markup. Ignoring data-rel-to \" +\n                    \"attribute of this step.\"\n                );\n            }\n        }\n\n        // While ``data-rel-reset=\"relative\"`` or just ``data-rel-reset``,\n        // ``data-rel-x/y/z`` and ``data-rel-rotate-x/y/z`` will have default value of 0,\n        // instead of inherit from previous slide.\n        //\n        // If ``data-rel-reset=\"all\"``, ``data-rotate-*`` are not inherited from previous slide too.\n        // So ``data-rel-reset=\"all\" data-rotate-x=\"90\"`` means\n        // ``data-rotate-x=\"90\" data-rotate-y=\"0\" data-rotate-z=\"0\"``, we doesn't need to\n        // bother clearing all unneeded attributes.\n\n        var inheritRotation = true;\n        if ( el.hasAttribute( \"data-rel-reset\" ) ) {\n\n            // Don't inherit from prev, just use the relative setting for current element\n            prev.relative = {\n                position: prev.relative.position,\n                x:0, y:0, z:0,\n                rotate: { x:0, y:0, z:0, order: \"xyz\" } };\n\n            if ( data.relReset === \"all\" ) {\n                inheritRotation = false;\n            }\n        }\n\n        var step = {\n                x: toNumberAdvanced( data.x, prev.x ),\n                y: toNumberAdvanced( data.y, prev.y ),\n                z: toNumberAdvanced( data.z, prev.z ),\n                rotate: {\n                    x: toNumber( data.rotateX, 0 ),\n                    y: toNumber( data.rotateY, 0 ),\n                    z: toNumber( data.rotateZ || data.rotate, 0 ),\n                    order: data.rotateOrder || \"xyz\"\n                },\n                relative: {\n                    position: data.relPosition || prev.relative.position,\n                    x: toNumberAdvanced( data.relX, prev.relative.x ),\n                    y: toNumberAdvanced( data.relY, prev.relative.y ),\n                    z: toNumberAdvanced( data.relZ, prev.relative.z ),\n                    rotate: {\n                        x: toNumber( data.relRotateX, prev.relative.rotate.x ),\n                        y: toNumber( data.relRotateY, prev.relative.rotate.y ),\n                        z: toNumber( data.relRotateZ, prev.relative.rotate.z ),\n                        order: data.rotateOrder || \"xyz\"\n                    }\n                }\n            };\n\n        // The final relatives maybe or maybe not the same with orignal data-rel-*\n        var relative = step.relative;\n\n        if ( step.relative.position === \"relative\" && inheritRotation ) {\n\n            // Calculate relatives based on previous slide\n            relative = api.lib.rotation.translateRelative(\n                step.relative, prev.rotate );\n\n            // Convert rotations to values that works with step.rotate\n            relative.rotate.x -= step.rotate.x;\n            relative.rotate.y -= step.rotate.y;\n            relative.rotate.z -= step.rotate.z;\n        }\n\n        // Relative position is ignored/zero if absolute is given.\n        // Note that this also has the effect of resetting any inherited relative values.\n        if ( data.x !== undefined ) {\n            relative.x = step.relative.x = 0;\n        }\n        if ( data.y !== undefined ) {\n            relative.y = step.relative.y = 0;\n        }\n        if ( data.z !== undefined ) {\n            relative.z = step.relative.z = 0;\n        }\n        if ( data.rotateX !== undefined || !inheritRotation ) {\n            relative.rotate.x = step.relative.rotate.x = 0;\n        }\n        if ( data.rotateY !== undefined || !inheritRotation ) {\n            relative.rotate.y = step.relative.rotate.y = 0;\n        }\n        if ( data.rotateZ !== undefined || data.rotate !== undefined || !inheritRotation ) {\n            relative.rotate.z = step.relative.rotate.z = 0;\n        }\n\n        step.x = step.x + relative.x;\n        step.y = step.y + relative.y;\n        step.z = step.z + relative.z;\n        step.rotate.x = step.rotate.x + relative.rotate.x;\n        step.rotate.y = step.rotate.y + relative.rotate.y;\n        step.rotate.z = step.rotate.z + relative.rotate.z;\n\n        return step;\n    };\n\n    var rel = function( root, impressApi ) {\n        api = impressApi;\n        toNumber = api.lib.util.toNumber;\n        toNumberAdvanced = api.lib.util.toNumberAdvanced;\n\n        var steps = root.querySelectorAll( \".step\" );\n        var prev;\n        startingState[ root.id ] = [];\n        for ( var i = 0; i < steps.length; i++ ) {\n            var el = steps[ i ];\n            startingState[ root.id ].push( {\n                el: el,\n                x: el.getAttribute( \"data-x\" ),\n                y: el.getAttribute( \"data-y\" ),\n                z: el.getAttribute( \"data-z\" ),\n                relX: el.getAttribute( \"data-rel-x\" ),\n                relY: el.getAttribute( \"data-rel-y\" ),\n                relZ: el.getAttribute( \"data-rel-z\" ),\n                rotateX: el.getAttribute( \"data-rotate-x\" ),\n                rotateY: el.getAttribute( \"data-rotate-y\" ),\n                rotateZ: el.getAttribute( \"data-rotate-z\" ),\n                rotate: el.getAttribute( \"data-rotate\" ),\n                relRotateX: el.getAttribute( \"data-rel-rotate-x\" ),\n                relRotateY: el.getAttribute( \"data-rel-rotate-y\" ),\n                relRotateZ: el.getAttribute( \"data-rel-rotate-z\" ),\n                relPosition: el.getAttribute( \"data-rel-position\" ),\n                rotateOrder: el.getAttribute( \"data-rotate-order\" )\n            } );\n            var step = computeRelativePositions( el, prev );\n\n            // Apply relative position (if non-zero)\n            el.setAttribute( \"data-x\", step.x );\n            el.setAttribute( \"data-y\", step.y );\n            el.setAttribute( \"data-z\", step.z );\n            el.setAttribute( \"data-rotate-x\", step.rotate.x );\n            el.setAttribute( \"data-rotate-y\", step.rotate.y );\n            el.setAttribute( \"data-rotate-z\", step.rotate.z );\n            el.setAttribute( \"data-rotate-order\", step.rotate.order );\n            el.setAttribute( \"data-rel-position\", step.relative.position );\n            el.setAttribute( \"data-rel-x\", step.relative.x );\n            el.setAttribute( \"data-rel-y\", step.relative.y );\n            el.setAttribute( \"data-rel-z\", step.relative.z );\n            el.setAttribute( \"data-rel-rotate-x\", step.relative.rotate.x );\n            el.setAttribute( \"data-rel-rotate-y\", step.relative.rotate.y );\n            el.setAttribute( \"data-rel-rotate-z\", step.relative.rotate.z );\n            prev = step;\n        }\n    };\n\n    // Register the plugin to be called in pre-init phase\n    window.impress.addPreInitPlugin( rel );\n\n    // Register teardown callback to reset the data.x, .y, .z values.\n    document.addEventListener( \"impress:init\", function( event ) {\n        var root = event.target;\n        event.detail.api.lib.gc.pushCallback( function() {\n            var steps = startingState[ root.id ];\n            var step;\n            var attrs = [\n                [ \"x\", \"relX\" ],\n                [ \"y\", \"relY\" ],\n                [ \"z\", \"relZ\" ],\n                [ \"rotate-x\", \"relRotateX\" ],\n                [ \"rotate-y\", \"relRotateY\" ],\n                [ \"rotate-z\", \"relRotateZ\" ],\n                [ \"rotate-order\", \"relRotateOrder\" ]\n            ];\n\n            while ( step = steps.pop() ) {\n\n                // Reset x/y/z in cases where this plugin has changed it.\n                for ( var i = 0; i < attrs.length; i++ ) {\n                    if ( step[ attrs[ i ][ 1 ] ] !== null ) {\n                        if ( step[ attrs[ i ][ 0 ] ] === null ) {\n                            step.el.removeAttribute( \"data-\" + attrs[ i ][ 0 ] );\n                        } else {\n                            step.el.setAttribute(\n                                \"data-\" + attrs[ i ][ 0 ], step[ attrs[ i ][ 0 ] ] );\n                        }\n                    }\n                }\n            }\n            delete startingState[ root.id ];\n        } );\n    }, false );\n} )( document, window );\n\n"
  },
  {
    "path": "src/plugins/resize/README.md",
    "content": "# Resize\n\nThis plugin resizes the presentation after a window resize. It does not offer any programmatic way of interaction, as this is not needed. It runs automatically in the background."
  },
  {
    "path": "src/plugins/resize/resize.js",
    "content": "/**\n * Resize plugin\n *\n * Rescale the presentation after a window resize.\n *\n * Copyright 2011-2012 Bartek Szopka (@bartaz)\n * Released under the MIT license.\n * ------------------------------------------------\n *  author:  Bartek Szopka\n *  version: 0.5.3\n *  url:     http://bartaz.github.com/impress.js/\n *  source:  http://github.com/bartaz/impress.js/\n *\n */\n\n/* global document, window */\n\n( function( document, window ) {\n    \"use strict\";\n\n    // Wait for impress.js to be initialized\n    document.addEventListener( \"impress:init\", function( event ) {\n        var api = event.detail.api;\n\n        // Rescale presentation when window is resized\n        api.lib.gc.addEventListener( window, \"resize\", api.lib.util.throttle( function() {\n\n            // Force going to active step again, to trigger rescaling\n            api.goto( document.querySelector( \".step.active\" ), 500 );\n        }, 250 ), false );\n    }, false );\n\n} )( document, window );\n\n"
  },
  {
    "path": "src/plugins/skip/README.md",
    "content": "Skip Plugin\n===========\n\nExample:\n\n        <!-- This slide is disabled in presentations, when moving with next()\n             and prev() commands, but you can still move directly to it, for\n             example with a url (anything using goto()). -->\n        <div class=\"step skip\">\n\nThe skip plugin is a pre-stepleave plugin. It is executed before \n`impress:stepleave` event. If the next step also has `class=\"skip\"`\nset, it will set the next step to the one after that.\n\nAuthor\n------\n\nCopyright 2016 Henrik Ingo (@henrikingo)\nReleased under the MIT license.\n\n"
  },
  {
    "path": "src/plugins/skip/skip.js",
    "content": "/**\n * Skip Plugin\n *\n * Example:\n *\n *    <!-- This slide is disabled in presentations, when moving with next()\n *         and prev() commands, but you can still move directly to it, for\n *         example with a url (anything using goto()). -->\n *         <div class=\"step skip\">\n *\n * Copyright 2016 Henrik Ingo (@henrikingo)\n * Released under the MIT license.\n */\n\n/* global document, window */\n\n( function( document, window ) {\n    \"use strict\";\n    var util;\n\n    document.addEventListener( \"impress:init\", function( event ) {\n        util = event.detail.api.lib.util;\n    }, false );\n\n    var getNextStep = function( el ) {\n        var steps = document.querySelectorAll( \".step\" );\n        for ( var i = 0; i < steps.length; i++ ) {\n            if ( steps[ i ] === el ) {\n                if ( i + 1 < steps.length ) {\n                    return steps[ i + 1 ];\n                } else {\n                    return steps[ 0 ];\n                }\n            }\n        }\n    };\n    var getPrevStep = function( el ) {\n        var steps = document.querySelectorAll( \".step\" );\n        for ( var i = steps.length - 1; i >= 0; i-- ) {\n            if ( steps[ i ] === el ) {\n                if ( i - 1 >= 0 ) {\n                    return steps[ i - 1 ];\n                } else {\n                    return steps[ steps.length - 1 ];\n                }\n            }\n        }\n    };\n\n    var skip = function( event ) {\n        if ( ( !event ) || ( !event.target ) ) {\n            return;\n        }\n\n        if ( event.detail.next.classList.contains( \"skip\" ) ) {\n            if ( event.detail.reason === \"next\" ) {\n\n                // Go to the next next step instead\n                event.detail.next = getNextStep( event.detail.next );\n\n                // Recursively call this plugin again, until there's a step not to skip\n                skip( event );\n            } else if ( event.detail.reason === \"prev\" ) {\n\n                // Go to the previous previous step instead\n                event.detail.next = getPrevStep( event.detail.next );\n                skip( event );\n            }\n\n            // If the new next element has its own transitionDuration, we're responsible for setting\n            // that on the event as well\n            event.detail.transitionDuration = util.toNumber(\n                event.detail.next.dataset.transitionDuration, event.detail.transitionDuration\n            );\n        }\n    };\n\n    // Register the plugin to be called in pre-stepleave phase\n    // The weight makes this plugin run early. This is a good thing, because this plugin calls\n    // itself recursively.\n    window.impress.addPreStepLeavePlugin( skip, 1 );\n\n} )( document, window );\n\n"
  },
  {
    "path": "src/plugins/stop/README.md",
    "content": "Stop Plugin\n===========\n\nExample:\n\n        <!-- Stop at this slide.\n             (For example, when used on the last slide, this prevents the \n             presentation from wrapping back to the beginning.) -->\n        <div class=\"step stop\">\n\nThe stop plugin is a pre-stepleave plugin. It is executed before \n`impress:stepleave` event. If the current slide has `class=\"stop\"`\nset, it will disable the next() command by setting the next slide to the current\nslide.\n\nAuthor\n------\n\nCopyright 2016 Henrik Ingo (@henrikingo)\nReleased under the MIT license.\n\n"
  },
  {
    "path": "src/plugins/stop/stop.js",
    "content": "/**\n * Stop Plugin\n *\n * Example:\n *\n *        <!-- Stop at this slide.\n *             (For example, when used on the last slide, this prevents the\n *             presentation from wrapping back to the beginning.) -->\n *        <div class=\"step stop\">\n *\n * Copyright 2016 Henrik Ingo (@henrikingo)\n * Released under the MIT license.\n */\n/* global document, window */\n( function( document, window ) {\n    \"use strict\";\n\n    var stop = function( event ) {\n        if ( ( !event ) || ( !event.target ) ) {\n            return;\n        }\n\n        if ( event.target.classList.contains( \"stop\" ) ) {\n            if ( event.detail.reason === \"next\" ) {\n                return false;\n            }\n        }\n    };\n\n    // Register the plugin to be called in pre-stepleave phase\n    // The weight makes this plugin run fairly early.\n    window.impress.addPreStepLeavePlugin( stop, 2 );\n\n} )( document, window );\n\n"
  },
  {
    "path": "src/plugins/substep/README.md",
    "content": "Substep Plugin\n===============\n\nReveal each substep (such as a bullet point) of the step separately. Just like in PowerPoint!\n\nIf the current step contains html elements with `class=\"substep\"` then this plugin will prevent a\n`prev()` / `next()` call to leave the slide, and instead reveal the next substep (for `next()`) or\nalternatively hide one (for `prev()`). Only once all substeps are shown, will a call to `next()`\nactually move to the next step, and only when all are hidden will a call to `prev()` move to the\nprevious one.\n\nBy default, this plugin reveals substeps in the order in which they appear in the HTML.  If you\nwould like to reveal them in a different order, you can supply an integer to `data-substep-order`.\nIf you do so, this plugin will reveal the substeps in ascending order; any substeps without a\nspecified `data-substep-order` will be revealed after all substeps with a specified order have\nbeen revealed.\n\nCalls to `goto()` will be ignored by this plugin, i.e. `goto()` will transition to whichever step is\nthe target.\n\nIn practice what happens is that when each substep is stepped through via `next()` calls, a\n`class=\"substep-visible\"` class is added to the element. It is up to the presentation author to\nuse the appropriate CSS to make the substeps hidden and visible.\n\nExample:\n\n        <style type=\"text/css\">\n            .substep { opacity: 0; }\n            .substep.substep-visible { opacity: 1; transition: opacity 1s; }\n        </style>\n\n        <div class=\"step\">\n            <h1>Fruits</h1>\n            <p class=\"substep\">Orange</p>\n            <p class=\"substep\">Apple</p>\n        </div>\n\nClasses:\n\n`substep-active` - The most recent substep in the current step\n\n`substep-visible` - The most recent and all previous substeps in the current step\n\nAuthor\n------\n\nCopyright 2017 Henrik Ingo (@henrikingo)\nReleased under the MIT license.\n\n"
  },
  {
    "path": "src/plugins/substep/substep.js",
    "content": "/**\n * Substep Plugin\n *\n * Copyright 2017 Henrik Ingo (@henrikingo)\n * Released under the MIT license.\n */\n\n/* global document, window */\n\n( function( document, window ) {\n    \"use strict\";\n\n    // Copied from core impress.js. Good candidate for moving to src/lib/util.js.\n    var triggerEvent = function( el, eventName, detail ) {\n        var event = document.createEvent( \"CustomEvent\" );\n        event.initCustomEvent( eventName, true, true, detail );\n        el.dispatchEvent( event );\n    };\n\n    var activeStep = null;\n    document.addEventListener( \"impress:stepenter\", function( event ) {\n        activeStep = event.target;\n    }, false );\n\n    var substep = function( event ) {\n        if ( ( !event ) || ( !event.target ) ) {\n            return;\n        }\n\n        var step = event.target;\n        var el; // Needed by jshint\n        if ( event.detail.reason === \"next\" ) {\n            el = showSubstepIfAny( step );\n            if ( el ) {\n\n                // Send a message to others, that we aborted a stepleave event.\n                triggerEvent( step, \"impress:substep:stepleaveaborted\",\n                              { reason: \"next\", substep: el } );\n\n                // Autoplay uses this for reloading itself\n                triggerEvent( step, \"impress:substep:enter\",\n                              { reason: \"next\", substep: el } );\n\n                // Returning false aborts the stepleave event\n                return false;\n            }\n        }\n        if ( event.detail.reason === \"prev\" ) {\n            el = hideSubstepIfAny( step );\n            if ( el ) {\n                triggerEvent( step, \"impress:substep:stepleaveaborted\",\n                              { reason: \"prev\", substep: el } );\n\n                triggerEvent( step, \"impress:substep:leave\",\n                              { reason: \"prev\", substep: el } );\n\n                return false;\n            }\n        }\n    };\n\n    var showSubstepIfAny = function( step ) {\n        var substeps = step.querySelectorAll( \".substep\" );\n        if ( substeps.length > 0 ) {\n            var sorted = sortSubsteps( substeps );\n            var visible = step.querySelectorAll( \".substep-visible\" );\n            return showSubstep( sorted, visible );\n        }\n    };\n\n    var sortSubsteps = function( substepNodeList ) {\n        var substeps = Array.from( substepNodeList );\n        var sorted = substeps\n            .filter( el => el.dataset.substepOrder )\n            .sort( ( a, b ) => {\n                var orderA = a.dataset.substepOrder;\n                var orderB = b.dataset.substepOrder;\n                return parseInt( orderA ) - parseInt( orderB );\n            } )\n            .concat( substeps.filter( el => {\n                return el.dataset.substepOrder === undefined;\n            } ) );\n        return sorted;\n    };\n\n    var showSubstep = function( substeps, visible ) {\n        if ( visible.length < substeps.length ) {\n            for ( var i = 0; i < substeps.length; i++ ) {\n                substeps[ i ].classList.remove( \"substep-active\" );\n            }\n\n            // Loop over all substeps that are not yet visible and set\n            //   those of currentSubstepOrder to visible and active\n            var el;\n            var currentSubstepOrder;\n            for ( var j = visible.length; j < substeps.length; j++ ) {\n                if ( currentSubstepOrder &&\n                    currentSubstepOrder !== substeps[ j ].dataset.substepOrder ) {\n\n                    // Stop if the substepOrder is greater\n                    break;\n                }\n                el = substeps[ j ];\n                currentSubstepOrder = el.dataset.substepOrder;\n                el.classList.add( \"substep-visible\" );\n                el.classList.add( \"substep-active\" );\n                if ( currentSubstepOrder === undefined ) {\n\n                    // Stop after one substep as default order\n                    break;\n                }\n            }\n\n            return el;\n        }\n    };\n\n    var hideSubstepIfAny = function( step ) {\n        var substeps = step.querySelectorAll( \".substep\" );\n        var visible = step.querySelectorAll( \".substep-visible\" );\n        var sorted = sortSubsteps( visible );\n        if ( substeps.length > 0 ) {\n            return hideSubstep( sorted );\n        }\n    };\n\n    var hideSubstep = function( visible ) {\n        if ( visible.length > 0 ) {\n            var current = -1;\n            for ( var i = 0; i < visible.length; i++ ) {\n                if ( visible[ i ].classList.contains( \"substep-active\" ) ) {\n                    current = i;\n                }\n                visible[ i ].classList.remove( \"substep-active\" );\n            }\n            if ( current > 0 ) {\n                visible[ current - 1 ].classList.add( \"substep-active\" );\n            }\n            var el = visible[ visible.length - 1 ];\n            el.classList.remove( \"substep-visible\" );\n\n            // Continue if there is another substep with the same substepOrder\n            if ( current > 0 &&\n                visible[ current ].dataset.substepOrder !== undefined &&\n                visible[ current - 1 ].dataset.substepOrder ===\n                visible[ current ].dataset.substepOrder ) {\n                visible.pop();\n                return hideSubstep( visible );\n            }\n            return el;\n        }\n    };\n\n    // Register the plugin to be called in pre-stepleave phase.\n    // The weight makes this plugin run before other preStepLeave plugins.\n    window.impress.addPreStepLeavePlugin( substep, 1 );\n\n    // When entering a step, in particular when re-entering, make sure that all substeps are hidden\n    // at first\n    document.addEventListener( \"impress:stepenter\", function( event ) {\n        var step = event.target;\n        var visible = step.querySelectorAll( \".substep-visible\" );\n        for ( var i = 0; i < visible.length; i++ ) {\n            visible[ i ].classList.remove( \"substep-visible\" );\n        }\n    }, false );\n\n    // API for others to reveal/hide next substep ////////////////////////////////////////////////\n    document.addEventListener( \"impress:substep:show\", function() {\n        showSubstepIfAny( activeStep );\n    }, false );\n\n    document.addEventListener( \"impress:substep:hide\", function() {\n        hideSubstepIfAny( activeStep );\n    }, false );\n\n} )( document, window );\n"
  },
  {
    "path": "src/plugins/toolbar/README.md",
    "content": "Toolbar plugin\n====================\n\nThis plugin provides a generic graphical toolbar. Other plugins that\nwant to expose a button or other widget, can add those to this toolbar.\n\nUsing a single consolidated toolbar for all GUI widgets makes it easier\nto position and style the toolbar rather than having to do that for lots\nof different divs.\n\nTo add/activate the toolbar in your presentation, add this div:\n\n    <div id=\"impress-toolbar\"></div>\n \nStyling the toolbar is left to presentation author. Here's an example CSS:\n\n    .impress-enabled div#impress-toolbar {\n        position: fixed;\n        right: 1px;\n        bottom: 1px;\n        opacity: 0.6;\n    }\n    .impress-enabled div#impress-toolbar > span {\n        margin-right: 10px;\n    }\n\nThe [mouse-timeout](../mouse-timeout/README.md) plugin can be leveraged to hide\nthe toolbar from sight, and only make it visible when mouse is moved.\n\n    body.impress-mouse-timeout div#impress-toolbar {\n        display: none;\n    }\n\nIf you're writing a plugin and would like to add a widget to the toolbar, see\n[the top of the source file for further instructions](toolbar.js).\n\n\nAuthor\n------\n\nHenrik Ingo (@henrikingo), 2016\n"
  },
  {
    "path": "src/plugins/toolbar/toolbar.js",
    "content": "/**\n * Toolbar plugin\n *\n * This plugin provides a generic graphical toolbar. Other plugins that\n * want to expose a button or other widget, can add those to this toolbar.\n *\n * Using a single consolidated toolbar for all GUI widgets makes it easier\n * to position and style the toolbar rather than having to do that for lots\n * of different divs.\n *\n *\n * *** For presentation authors: *****************************************\n *\n * To add/activate the toolbar in your presentation, add this div:\n *\n *     <div id=\"impress-toolbar\"></div>\n *\n * Styling the toolbar is left to presentation author. Here's an example CSS:\n *\n *    .impress-enabled div#impress-toolbar {\n *        position: fixed;\n *        right: 1px;\n *        bottom: 1px;\n *        opacity: 0.6;\n *    }\n *    .impress-enabled div#impress-toolbar > span {\n *        margin-right: 10px;\n *    }\n *\n * The [mouse-timeout](../mouse-timeout/README.md) plugin can be leveraged to hide\n * the toolbar from sight, and only make it visible when mouse is moved.\n *\n *    body.impress-mouse-timeout div#impress-toolbar {\n *        display: none;\n *    }\n *\n *\n * *** For plugin authors **********************************************\n *\n * To add a button to the toolbar, trigger the `impress:toolbar:appendChild`\n * or `impress:toolbar:insertBefore` events as appropriate. The detail object\n * should contain following parameters:\n *\n *    { group : 1,                       // integer. Widgets with the same group are grouped inside\n *                                       // the same <span> element.\n *      html : \"<button>Click</button>\", // The html to add.\n *      callback : \"mycallback\",         // Toolbar plugin will trigger event\n *                                       // `impress:toolbar:added:mycallback` when done.\n *      before: element }                // The reference element for an insertBefore() call.\n *\n * You should also listen to the `impress:toolbar:added:mycallback` event. At\n * this point you can find the new widget in the DOM, and for example add an\n * event listener to it.\n *\n * You are free to use any integer for the group. It's ok to leave gaps. It's\n * ok to co-locate with widgets for another plugin, if you think they belong\n * together.\n *\n * See navigation-ui for an example.\n *\n * Copyright 2016 Henrik Ingo (@henrikingo)\n * Released under the MIT license.\n */\n\n/* global document */\n\n( function( document ) {\n    \"use strict\";\n    var toolbar = document.getElementById( \"impress-toolbar\" );\n    var groups = [];\n\n    /**\n     * Get the span element that is a child of toolbar, identified by index.\n     *\n     * If span element doesn't exist yet, it is created.\n     *\n     * Note: Because of Run-to-completion, this is not a race condition.\n     * https://developer.mozilla.org/en/docs/Web/JavaScript/EventLoop#Run-to-completion\n     *\n     * :param: index   Method will return the element <span id=\"impress-toolbar-group-{index}\">\n     */\n    var getGroupElement = function( index ) {\n        var id = \"impress-toolbar-group-\" + index;\n        if ( !groups[ index ] ) {\n            groups[ index ] = document.createElement( \"span\" );\n            groups[ index ].id = id;\n            var nextIndex = getNextGroupIndex( index );\n            if ( nextIndex === undefined ) {\n                toolbar.appendChild( groups[ index ] );\n            } else {\n                toolbar.insertBefore( groups[ index ], groups[ nextIndex ] );\n            }\n        }\n        return groups[ index ];\n    };\n\n    /**\n     * Get the span element from groups[] that is immediately after given index.\n     *\n     * This can be used to find the reference node for an insertBefore() call.\n     * If no element exists at a larger index, returns undefined. (In this case,\n     * you'd use appendChild() instead.)\n     *\n     * Note that index needn't itself exist in groups[].\n     */\n    var getNextGroupIndex = function( index ) {\n        var i = index + 1;\n        while ( !groups[ i ] && i < groups.length ) {\n            i++;\n        }\n        if ( i < groups.length ) {\n            return i;\n        }\n    };\n\n    // API\n    // Other plugins can add and remove buttons by sending them as events.\n    // In return, toolbar plugin will trigger events when button was added.\n    if ( toolbar ) {\n        /**\n         * Append a widget inside toolbar span element identified by given group index.\n         *\n         * :param: e.detail.group    integer specifying the span element where widget will be placed\n         * :param: e.detail.element  a dom element to add to the toolbar\n         */\n        toolbar.addEventListener( \"impress:toolbar:appendChild\", function( e ) {\n            var group = getGroupElement( e.detail.group );\n            group.appendChild( e.detail.element );\n        } );\n\n        /**\n         * Add a widget to toolbar using insertBefore() DOM method.\n         *\n         * :param: e.detail.before   the reference dom element, before which new element is added\n         * :param: e.detail.element  a dom element to add to the toolbar\n         */\n        toolbar.addEventListener( \"impress:toolbar:insertBefore\", function( e ) {\n            toolbar.insertBefore( e.detail.element, e.detail.before );\n        } );\n\n        /**\n         * Remove the widget in e.detail.remove.\n         */\n        toolbar.addEventListener( \"impress:toolbar:removeWidget\", function( e ) {\n            toolbar.removeChild( e.detail.remove );\n        } );\n\n        document.addEventListener( \"impress:init\", function( event ) {\n            var api = event.detail.api;\n            api.lib.gc.pushCallback( function() {\n                toolbar.innerHTML = \"\";\n                groups = [];\n            } );\n        } );\n    } // If toolbar\n\n} )( document );\n"
  },
  {
    "path": "src/plugins/touch/README.md",
    "content": "# Touch\n\nThis plugin handles touch input. You can use swipe gestures to interact with your presentation, just note that the transitions might look slightly different from what you are used to on the PC."
  },
  {
    "path": "src/plugins/touch/touch.js",
    "content": "/**\n * Support for swipe and tap on touch devices\n *\n * This plugin implements navigation for plugin devices, via swiping left/right,\n * or tapping on the left/right edges of the screen.\n *\n *\n *\n * Copyright 2015: Andrew Dunai (@and3rson)\n * Modified to a plugin, 2016: Henrik Ingo (@henrikingo)\n *\n * MIT License\n */\n/* global document, window */\n( function( document, window ) {\n    \"use strict\";\n\n    // Touch handler to detect swiping left and right based on window size.\n    // If the difference in X change is bigger than 1/20 of the screen width,\n    // we simply call an appropriate API function to complete the transition.\n    var startX = 0;\n    var lastX = 0;\n    var lastDX = 0;\n    var threshold = window.innerWidth / 20;\n\n    document.addEventListener( \"touchstart\", function( event ) {\n        lastX = startX = event.touches[ 0 ].clientX;\n    } );\n\n    document.addEventListener( \"touchmove\", function( event ) {\n         var x = event.touches[ 0 ].clientX;\n         var diff = x - startX;\n\n         // To be used in touchend\n         lastDX = lastX - x;\n         lastX = x;\n\n         window.impress().swipe( diff / window.innerWidth );\n     } );\n\n     document.addEventListener( \"touchend\", function() {\n         var totalDiff = lastX - startX;\n         if ( Math.abs( totalDiff ) > window.innerWidth / 5 && ( totalDiff * lastDX ) <= 0 ) {\n             if ( totalDiff > window.innerWidth / 5 && lastDX <= 0 ) {\n                 window.impress().prev();\n             } else if ( totalDiff < -window.innerWidth / 5 && lastDX >= 0 ) {\n                 window.impress().next();\n             }\n         } else if ( Math.abs( lastDX ) > threshold ) {\n             if ( lastDX < -threshold ) {\n                 window.impress().prev();\n             } else if ( lastDX > threshold ) {\n                 window.impress().next();\n             }\n         } else {\n\n             // No movement - move (back) to the current slide\n             window.impress().goto( document.querySelector( \"#impress .step.active\" ) );\n         }\n     } );\n\n     document.addEventListener( \"touchcancel\", function() {\n\n             // Move (back) to the current slide\n             window.impress().goto( document.querySelector( \"#impress .step.active\" ) );\n     } );\n\n} )( document, window );\n"
  },
  {
    "path": "test/HOWTO.md",
    "content": "Testing HowTo\n=============\n\nInstall and run tests\n---------------------\n\n    npm install\n    npm run test\n    npm run lint\n\nQUnit is used for unit tests. Above npm command will run them under karma. You can also run the\nsame tests as plain qunit in Firefox (but not Chrome, due to use of iframe):\n\n    firefox qunit_test_runner.html\n\nFor linting both jshint and jscs are used. As is customary, they are configured via\n[.jshintrc](.jshintrc) and [.jscsrc](.jscsrc).\n\n\nAnatomy of a unit test\n----------------------\n\nImpress.js likes to use the entire browser window, sets classes on the body element, and so on. For\nthis reason, we use an iframe to run each test. QUnit tests are supposed to run inside a\n`<div id=\"qunit-fixture\">` element. So in our case, the contents of that div is an iframe.\n\nEach test consists of 2 files: A html file that contains a normal impress.js presentation, and\na js file that contains your QUnit tests. For example, see\n[test/core_tests_presentation.html](test/core_tests_presentation.html) and\n[test/core_tests.js](test/core_tests.js). Note that the QUnit tests run in the parent window.\n\n[test/helpers.js](test/helpers.js) contains helper functions to create the iframe and load the\nhtml file that contains the impress.js presentation to be tested.\n\nAn example test would therefore look like:\n\n    QUnit.test( \"Example tests\", function( assert ) {\n      loadIframe( \"test/core_tests_presentation.html\", assert, function() {\n        initPresentation( assert, function() {\n          var iframe = document.getElementById( \"presentation-iframe\" );\n          var iframeDoc = iframe.contentDocument;\n          var iframeWin = iframe.contentWindow;\n          var step1 = iframeDoc.querySelector( \"div#step-1\" );\n\n          assert.equal( step1.dataset.x, \"0\", \"data-x attribute set to zero\" );\n          assert.equal( step1.dataset.y, \"0\", \"data-y attribute set to zero\" );\n\n\nWhere to save unit tests?\n-------------------------\n\nTests related to a plugin are saved in the plugin folder. See for example\n[src/plugins/navigation/navigation_tests.js](src/plugins/navigation/navigation_tests.js).\n\nTests for impress.js core, or other tests not related to one specific plugin, go under\n[test/](test/).\n\nAdding your js file to the right places\n---------------------------------------\n\nThere are 3 files where you need to add your new test so that it gets run. (Yeah, see\n[#658](https://github.com/impress/impress.js/issues/658) for more on that topic...)\n\n1. qunit_test_runner.html\n2. karma.conf.js\n3. karma.conf-sauce.js\n\n"
  },
  {
    "path": "test/core_tests.js",
    "content": "/*\n * Copyright 2016 Henrik Ingo (@henrikingo)\n *\n * Released under the MIT license. See LICENSE file.\n */\n\n/* global document, console, setTimeout, loadIframe, initPresentation, _impressSupported, QUnit */\n\nQUnit.module( \"Core Tests\" );\n\nQUnit.test( \"Initialize Impress.js\", function( assert ) {\n  console.log( \"Begin init() test\" );\n\n  // Init triggers impress:init and impress:stepenter events, which we want to catch.\n  var doneInit      = assert.async();\n  var doneStepEnter = assert.async();\n  var doneSync      = assert.async();\n\n  loadIframe( \"test/core_tests_presentation.html\", assert, function() {\n    var iframe = document.getElementById( \"presentation-iframe\" );\n    var iframeDoc = iframe.contentDocument;\n    var iframeWin = iframe.contentWindow;\n    var root  = iframeDoc.querySelector( \"div#impress\" );\n\n    // Catch events triggered by init()\n    var assertInit = function() {\n      assert.ok( true, \"impress:init event triggered.\" );\n\n      var canvas = iframeDoc.querySelector( \"div#impress > div\" );\n\n      // Delay and duration don't become set before the first transition actually happened\n      assert.equal( canvas.style.transitionDelay,\n                    \"0ms\",\n                    \"canvas.style.transitionDelay initialized correctly\" );\n      assert.equal( canvas.style.transitionDuration,\n                    \"0ms\",\n                    \"canvas.style.transitionDuration initialized correctly\" );\n\n      doneInit();\n      console.log( \"End init() test (async)\" );\n    };\n\n    var assertInitWrapper = function() {\n      setTimeout( function() { assertInit(); }, 10 );\n    };\n    root.addEventListener( \"impress:init\", assertInitWrapper );\n\n    root.addEventListener( \"impress:stepenter\", function( event ) {\n      assert.ok( true, \"impress:stepenter event triggered.\" );\n      var step1 = iframeDoc.querySelector( \"div#step-1\" );\n      assert.equal( event.target, step1,\n                    event.target.id + \" triggered impress:stepenter event.\" );\n      doneStepEnter();\n    } );\n\n    // Synchronous code and assertions\n    assert.ok( iframeWin.impress,\n               \"impress declared in global scope\" );\n    assert.strictEqual( iframeWin.impress().init(), undefined,\n                        \"impress().init() called.\" );\n    assert.strictEqual( iframeWin.impress().init(), undefined,\n                        \"It's ok to call impress().init() a second time, it's a no-op.\" );\n\n    // The asserts below are true immediately after impress().init() returns.\n    // Therefore we test them here, not in an event handler.\n    var notSupportedClass = iframeDoc.body.classList.contains( \"impress-not-supported\" );\n    var yesSupportedClass = iframeDoc.body.classList.contains( \"impress-supported\" );\n    if ( !_impressSupported() ) {\n      assert.ok( notSupportedClass,\n                 \"body.impress-not-supported class still there.\" );\n      assert.ok( !yesSupportedClass,\n                 \"body.impress-supported class was NOT added.\" );\n    } else {\n      assert.ok( !notSupportedClass,\n                 \"body.impress-not-supported class was removed.\" );\n      assert.ok( yesSupportedClass,\n                 \"body.impress-supported class was added.\" );\n\n      assert.ok( !iframeDoc.body.classList.contains( \"impress-disabled\" ),\n                 \"body.impress-disabled is removed.\" );\n      assert.ok( iframeDoc.body.classList.contains( \"impress-enabled\" ),\n                 \"body.impress-enabled is added.\" );\n\n      var canvas = iframeDoc.querySelector( \"div#impress > div\" );\n      assert.ok( !canvas.classList.contains( \"step\" ) && canvas.id === \"\",\n                 \"Additional 'canvas' div inserted between div#impress root and steps.\" );\n      assert.equal( canvas.style.transform,\n                    \"rotateZ(0deg) rotateY(0deg) rotateX(0deg) translate3d(1000px, 0px, 0px)\",\n                    \"canvas.style.transform initialized correctly\" );\n      assert.ok( canvas.style.transformOrigin === \"left top 0px\" ||\n                 canvas.style.transformOrigin ===  \"left top\",\n                    \"canvas.style.transformOrigin initialized correctly\" );\n      assert.equal( canvas.style.transformStyle,\n                    \"preserve-3d\",\n                    \"canvas.style.transformStyle initialized correctly\" );\n      assert.equal( canvas.style.transitionProperty,\n                    \"all\",\n                    \"canvas.style.transitionProperty initialized correctly\" );\n      assert.equal( canvas.style.transitionTimingFunction,\n                    \"ease-in-out\",\n                    \"canvas.style.transitionTimingFunction initialized correctly\" );\n\n      assert.equal( iframeDoc.documentElement.style.height,\n                    \"100%\",\n                    \"documentElement.style.height is 100%\" );\n\n      // Steps initialization\n      var step1 = iframeDoc.querySelector( \"div#step-1\" );\n      assert.equal( step1.style.position,\n                    \"absolute\",\n                    \"Step position is 'absolute'.\" );\n\n      assert.ok( step1.classList.contains( \"active\" ),\n                 \"Step 1 has active css class.\" );\n\n    }\n    doneSync();\n    console.log( \"End init() test (sync)\" );\n  } ); // LoadIframe()\n\n} );\n\n// Note: Here we focus on testing the core functionality of moving between\n// steps, the css classes set and unset, and events triggered.\n// TODO: more complex animations and check position, transitions, delays, etc...\nQUnit.test( \"Impress Core API\", function( assert ) {\n  console.log( \"Begin core api test\" );\n  var done = assert.async();\n  loadIframe( \"test/core_tests_presentation.html\", assert, function() {\n    initPresentation( assert, function() {\n      var iframe = document.getElementById( \"presentation-iframe\" );\n      var iframeDoc = iframe.contentDocument;\n      var iframeWin = iframe.contentWindow;\n\n      // Impress.js itself uses event listeners to manipulate most CSS classes.\n      // Wait a short while before checking, to avoid race.\n      // (See assertStepEnterWrapper and assertStepLeaveWrapper.)\n      var wait = 5; // Milliseconds\n\n      var step1 = iframeDoc.querySelector( \"div#step-1\" );\n      var step2 = iframeDoc.querySelector( \"div#step-2\" );\n      var step3 = iframeDoc.querySelector( \"div#step-3\" );\n      var step4 = iframeDoc.querySelector( \"div#fourth\" );\n      var root  = iframeDoc.querySelector( \"div#impress\" );\n\n      // On impress:stepenter, we do some assertions on the \"entered\" object.\n      // On impress:stepleave, we do some assertions on the \"left\" object.\n      // Finally we call next() to initialize the next transition, and it starts all over again.\n      var i = 0;\n      var sequence = [ { left: step1,\n                         entered: step2,\n                         next: function() { return iframeWin.impress().goto( 2 ); },\n                         text: \"goto(<number>) called and returns ok (2->3)\" },\n                       { left: step2,\n                         entered: step3,\n                         next: function() { return iframeWin.impress().goto( \"fourth\" ); },\n                         text: \"goto(<string>) called and returns ok (3->4)\" },\n                       { left: step3,\n                         entered: step4,\n                         next: function() { return iframeWin.impress().next(); },\n                         text: \"next() wraps around to first step (4->1)\" },\n                       { left: step4,\n                         entered: step1,\n                         next: function() { return iframeWin.impress().prev(); },\n                         text: \"prev() wraps around to last step (1->4)\" },\n                       { left: step1,\n                         entered: step4,\n                         next: function() { return iframeWin.impress().prev(); },\n                         text: \"prev() called and returns ok (4->3)\" },\n                       { left: step4,\n                         entered: step3,\n                         next: function() { return iframeWin.impress().goto( 0 ); },\n                         text: \"End of test suite, return to first step with goto(0).\" },\n                       { left: step3,\n                         entered: step1,\n                         next: false } // False = end of sequence\n      ];\n\n      // When both assertStepEnter and assertStepLeave are done, we can go to next step in sequence.\n      var readyCount = 0;\n      var readyForNext = function() {\n        readyCount++;\n        if ( readyCount % 2 === 0 ) {\n          if ( sequence[ i ].next ) {\n            assert.ok( sequence[ i ].next(), sequence[ i ].text );\n            i++;\n            assertImmediately();\n          } else {\n            done();\n            console.log( \"End core api test\" );\n          }\n        }\n      };\n\n      // Things to check on impress:stepenter event -----------------------------//\n      var assertStepEnter = function( event ) {\n        assert.equal( event.target, sequence[ i ].entered,\n                      event.target.id + \" triggered impress:stepenter event.\" );\n        assert.ok( event.target.classList.contains( \"present\" ),\n                   event.target.id + \" set present css class.\" );\n        assert.ok( !event.target.classList.contains( \"future\" ),\n                   event.target.id + \" unset future css class.\" );\n        assert.ok( !event.target.classList.contains( \"past\" ),\n                   event.target.id + \" unset past css class.\" );\n        assert.equal( \"#/\" + event.target.id, iframeWin.location.hash,\n                      \"Hash is \" + \"#/\" + event.target.id );\n\n        // Just by way of comparison, check transitionDuration again, in a non-init transition\n        var canvas = iframeDoc.querySelector( \"div#impress > div\" );\n        assert.equal( canvas.style.transitionDelay,\n                      \"0ms\",\n                      \"canvas.style.transitionDelay set correctly\" );\n        assert.equal( canvas.style.transitionDuration,\n                      \"1000ms\",\n                      \"canvas.style.transitionDuration set correctly\" );\n\n        readyForNext();\n      };\n\n      var assertStepEnterWrapper = function( event ) {\n        setTimeout( function() { assertStepEnter( event ); }, wait );\n      };\n      root.addEventListener( \"impress:stepenter\", assertStepEnterWrapper );\n\n      // Things to check on impress:stepleave event -----------------------------//\n      var assertStepLeave = function( event ) {\n        assert.equal( event.target, sequence[ i ].left,\n                      event.target.id + \" triggered impress:stepleave event.\" );\n        assert.ok( !event.target.classList.contains( \"present\" ),\n                   event.target.id + \" unset present css class.\" );\n        assert.ok( !event.target.classList.contains( \"future\" ),\n                   event.target.id + \" unset future css class.\" );\n        assert.ok( event.target.classList.contains( \"past\" ),\n                   event.target.id + \" set past css class.\" );\n        readyForNext();\n      };\n\n      var assertStepLeaveWrapper = function( event ) {\n        setTimeout( function() { assertStepLeave( event ); }, wait );\n      };\n      root.addEventListener( \"impress:stepleave\", assertStepLeaveWrapper );\n\n      // Things to check immediately after impress().goto() ---------------------------//\n      var assertImmediately = function() {\n        assert.ok( sequence[ i ].entered.classList.contains( \"active\" ),\n                   sequence[ i ].entered.id + \" set active css class.\" );\n        assert.ok( !sequence[ i ].left.classList.contains( \"active\" ),\n                   sequence[ i ].left.id + \" unset active css class.\" );\n      };\n\n      // Done with setup. Start testing! -----------------------------------------------//\n      // Do no-op tests first, then trigger the sequence of transitions we setup above. //\n\n      assert.strictEqual( iframeWin.impress().goto( iframeDoc.querySelector( \"div#impress\" ) ),\n                        false,\n                        \"goto() to a non-step element fails, as it should.\" );\n      assert.strictEqual( iframeWin.impress().goto(),\n                        false,\n                        \"goto(<nothing>) fails, as it should.\" );\n\n      // This starts executing the sequence above\n      assert.ok( iframeWin.impress().next(),\n                 \"next() called and returns ok (1->2)\" );\n    } ); // InitPresentation()\n  } ); // LoadIframe()\n} );\n"
  },
  {
    "path": "test/core_tests_presentation.html",
    "content": "<!DOCTYPE html>\n  <!--\n  Copyright 2016 Henrik Ingo (@henrikingo)\n  Released under the MIT license. See LICENSE file.\n  -->\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>The presentation steps used in an iframe in core_tests.js</title>\n</head>\n<!-- id is used by syn -->\n<body class=\"impress-not-supported\" id=\"bodyid\">\n  <div id=\"impress\">\n    <div class=\"step\" data-x=\"-1000\" data-y=\"0\">First slide</div>\n    <div class=\"step\" data-x=\"-800\" data-y=\"0\">Second slide <br /><a href=\"#fourth\" id=\"linktofourth\">link to fourth slide</a></div>\n    <div class=\"step\" data-x=\"-600\" data-y=\"0\">Third slide<br /><a href=\"#step-1\" id=\"linktofirst\">link to first slide</a></div>\n    <div class=\"step\" id=\"fourth\" data-x=\"-400\" data-y=\"0\">Fourth slide</div>\n  </div>\n  <script src=\"../js/impress.js\"></script>\n  <!-- See http://bitovi.com/blog/2010/07/syn-a-standalone-synthetic-event-library.html for simple usage guide. -->\n  <script src=\"../node_modules/syn/dist/global/syn.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "test/helpers.js",
    "content": "// This file contains so much HTML, that we will just respectfully disagree about js\n/* jshint quotmark:single */\n/* global document, console, setTimeout, navigator, QUnit */\n/* exported loadIframe, initPresentation, _impressSupported */\n\n// Log all QUnit assertions to console.log(), so that they are visible in karma output\nQUnit.log( function( details ) {\n  console.log( 'QUnit.log: ', details.result, details.message );\n} );\n\nvar loadIframe = function( src, assert, callback ) {\n  console.log( 'Begin loadIframe' );\n\n  // When running in Karma, the #qunit-fixture appears from somewhere and we can't set its\n  // contents in advance, so we set it now.\n  var fix = document.getElementById( 'qunit-fixture' );\n  fix.innerHTML = [\n    '\\n',\n    '    <iframe id=\"presentation-iframe\"\\n',\n    '            width=\"595\" height=\"485\"\\n',\n    '            frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\"\\n',\n    '            style=\"border:1px solid #CCC; max-width: 100%;\">\\n',\n    '    </iframe>'\n    ].join( '' );\n\n  var iframe = document.getElementById( 'presentation-iframe' );\n\n  var onLoad = function() {\n    assert.ok( true,\n               'Presentation loaded. iframe.src = ' + iframe.src );\n    try {\n      assert.ok( iframe.contentDocument,\n                 'Verifying that tests can access the presentation inside the iframe. ' +\n                 'Note: On Firefox this fails when using paths with \"../\" parts for the iframe.' );\n    }\n    catch ( err ) {\n      assert.ok( false,\n               'Error when trying to access presentation in iframe. Note: When using Chrome with ' +\n               'local files (file:///) this will fail with SecurityError. ' +\n               'You can however use Chrome over Karma.' );\n    }\n    console.log( 'End loadIframe' );\n    callback();\n  };\n\n  iframe.addEventListener( 'load', onLoad );\n\n  assert.ok( iframe.src = src,\n             'Setting iframe.src = ' + src );\n};\n\nvar initPresentation = function( assert, callback, rootId ) {\n  console.log( 'Begin initPresentation' );\n  var iframe = document.getElementById( 'presentation-iframe' );\n  var iframeDoc = iframe.contentDocument;\n  var iframeWin = iframe.contentWindow;\n\n  // Impress:stepenter is the last event triggered in init(), so we wait for that.\n  var waitForStepEnter = function( event ) {\n    assert.ok( true, 'impress (' + event.target.id + ') is now initialized.' );\n    iframeDoc.removeEventListener( 'impress:stepenter', waitForStepEnterWrapper );\n    console.log( 'End initPresentation' );\n    callback();\n  };\n\n  // Unfortunately, impress.js uses the impress:stepenter event internally to\n  // do some things related to entering a step. This causes a race condition when\n  // we listen for the same event and expect it to be done with everything.\n  // We wait 5 ms to resolve the race condition, then it's safe to start testing.\n  var waitForStepEnterWrapper = function( event ) {\n    setTimeout( function() { waitForStepEnter( event ); }, 5 );\n  };\n  iframeDoc.addEventListener( 'impress:stepenter', waitForStepEnterWrapper );\n\n  assert.strictEqual( iframeWin.impress( rootId ).init(), undefined, 'Initializing impress.' );\n};\n\n// Helper function to determine whether this browser is supported by\n// impress.js or not. Copied from impress.js itself.\nvar _impressSupported = function() {\n  var pfx = ( function() {\n    var style = document.createElement( 'dummy' ).style,\n        prefixes = 'Webkit Moz O ms Khtml'.split( ' ' ),\n        memory = {};\n    return function( prop ) {\n      if ( typeof memory[ prop ] === 'undefined' ) {\n        var ucProp  = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ),\n            props   = ( prop + ' ' + prefixes.join( ucProp + ' ' ) + ucProp ).split( ' ' );\n        memory[ prop ] = null;\n        for ( var i in props ) {\n            if ( style[ props[ i ] ] !== undefined ) {\n                memory[ prop ] = props[ i ];\n                break;\n            }\n        }\n      }\n      return memory[ prop ];\n    };\n  } )();\n\n  var ua = navigator.userAgent.toLowerCase();\n  return ( pfx( 'perspective' ) !== null ) &&\n           ( document.body.classList ) &&\n           ( document.body.dataset ) &&\n           ( ua.search( /(iphone)|(ipod)|(android)/ ) === -1 );\n};\n\n"
  },
  {
    "path": "test/non_default.html",
    "content": "<!DOCTYPE html>\n  <!--\n  Copyright 2016 Henrik Ingo (@henrikingo)\n  Released under the MIT license. See LICENSE file.\n  -->\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>A test presentation with non-default value for the root div id</title>\n</head>\n<body class=\"impress-not-supported\">\n  <div id=\"non-default-id\">\n    <div class=\"step\" data-x=\"-1000\" data-y=\"0\">First slide</div>\n    <div class=\"step\" data-x=\"-800\" data-y=\"0\">Second slide</div>\n  </div>\n  <script src=\"../js/impress.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "test/non_default.js",
    "content": "/*\n * Copyright 2016 Henrik Ingo (@henrikingo)\n *\n * Released under the MIT license. See LICENSE file.\n */\n\n/* global document, console, setTimeout, loadIframe, initPresentation, _impressSupported, QUnit */\n\nQUnit.module( \"Non Default Values\" );\n\nQUnit.test( \"Initialize Impress.js\", function( assert ) {\n  console.log( \"Begin init() test\" );\n\n  // Init triggers impress:init and impress:stepenter events, which we want to catch.\n  var doneInit      = assert.async();\n  var doneStepEnter = assert.async();\n  var doneSync      = assert.async();\n\n  loadIframe( \"test/non_default.html\", assert, function() {\n    var iframe = document.getElementById( \"presentation-iframe\" );\n    var iframeDoc = iframe.contentDocument;\n    var iframeWin = iframe.contentWindow;\n    var root  = iframeDoc.querySelector( \"div#non-default-id\" );\n\n    // Catch events triggered by init()\n    var assertInit = function() {\n      assert.ok( true, \"impress:init event triggered.\" );\n\n      doneInit();\n      console.log( \"End init() test (async)\" );\n    };\n\n    var assertInitWrapper = function() {\n      setTimeout( function() { assertInit(); }, 10 );\n    };\n    root.addEventListener( \"impress:init\", assertInitWrapper );\n\n    root.addEventListener( \"impress:stepenter\", function( event ) {\n      assert.ok( true, \"impress:stepenter event triggered.\" );\n      var step1 = iframeDoc.querySelector( \"div#step-1\" );\n      assert.equal( event.target, step1,\n                    event.target.id + \" triggered impress:stepenter event.\" );\n      doneStepEnter();\n    } );\n\n    // Synchronous code and assertions\n    assert.ok( iframeWin.impress,\n               \"impress declared in global scope\" );\n    assert.strictEqual( iframeWin.impress( \"non-default-id\" ).init(), undefined,\n                        \"impress().init() called with 'non-default-id'.\" );\n    assert.strictEqual( iframeWin.impress( \"non-default-id\" ).init(), undefined,\n                        \"It's ok to call impress().init() a second time, it's a no-op.\" );\n\n    // The asserts below are true immediately after impress().init() returns.\n    // Therefore we test them here, not in an event handler.\n    var notSupportedClass = iframeDoc.body.classList.contains( \"impress-not-supported\" );\n    var yesSupportedClass = iframeDoc.body.classList.contains( \"impress-supported\" );\n    if ( !_impressSupported() ) {\n      assert.ok( notSupportedClass,\n                 \"body.impress-not-supported class still there.\" );\n      assert.ok( !yesSupportedClass,\n                 \"body.impress-supported class was NOT added.\" );\n    } else {\n      assert.ok( !notSupportedClass,\n                 \"body.impress-not-supported class was removed.\" );\n      assert.ok( yesSupportedClass,\n                 \"body.impress-supported class was added.\" );\n\n      assert.ok( !iframeDoc.body.classList.contains( \"impress-disabled\" ),\n                 \"body.impress-disabled is removed.\" );\n      assert.ok( iframeDoc.body.classList.contains( \"impress-enabled\" ),\n                 \"body.impress-enabled is added.\" );\n\n      // Steps initialization\n      var step1 = iframeDoc.querySelector( \"div#step-1\" );\n      assert.equal( step1.style.position,\n                    \"absolute\",\n                    \"Step position is 'absolute'.\" );\n\n      assert.ok( step1.classList.contains( \"active\" ),\n                 \"Step 1 has active css class.\" );\n\n    }\n    doneSync();\n    console.log( \"End init() test (sync)\" );\n  } ); // LoadIframe()\n\n} );\n\nQUnit.test( \"Non default root id, API tests\", function( assert ) {\n  console.log( \"Begin api test\" );\n  var done = assert.async();\n  loadIframe( \"test/non_default.html\", assert, function() {\n    initPresentation( assert, function() {\n      var iframe = document.getElementById( \"presentation-iframe\" );\n      var iframeDoc = iframe.contentDocument;\n      var iframeWin = iframe.contentWindow;\n\n      var wait = 5; // Milliseconds\n\n      var step1 = iframeDoc.querySelector( \"div#step-1\" );\n      var step2 = iframeDoc.querySelector( \"div#step-2\" );\n      var root  = iframeDoc.querySelector( \"div#non-default-id\" );\n\n      // Things to check on impress:stepenter event -----------------------------//\n      var assertStepEnter = function( event ) {\n        assert.equal( event.target, step2,\n                      event.target.id + \" triggered impress:stepenter event.\" );\n        assert.ok( event.target.classList.contains( \"present\" ),\n                   event.target.id + \" set present css class.\" );\n        assert.ok( !event.target.classList.contains( \"future\" ),\n                   event.target.id + \" unset future css class.\" );\n        assert.ok( !event.target.classList.contains( \"past\" ),\n                   event.target.id + \" unset past css class.\" );\n        assert.equal( \"#/\" + event.target.id, iframeWin.location.hash,\n                      \"Hash is \" + \"#/\" + event.target.id );\n\n        done();\n      };\n\n      var assertStepEnterWrapper = function( event ) {\n        setTimeout( function() { assertStepEnter( event ); }, wait );\n      };\n      root.addEventListener( \"impress:stepenter\", assertStepEnterWrapper );\n\n      // Done with setup. Start testing! -----------------------------------------------//\n\n      assert.strictEqual( iframeWin.impress( \"non-default-id\" ).goto(),\n                        false,\n                        \"goto(<nothing>) fails, as it should.\" );\n\n      // This starts executing the sequence above\n      assert.ok( iframeWin.impress( \"non-default-id\" ).next(),\n                 \"impress('non-default-id').next() called and returns ok (1->2)\" );\n\n      // Things to check immediately after impress().goto() ---------------------------//\n      assert.ok( step2.classList.contains( \"active\" ),\n                 step2.id + \" set active css class.\" );\n      assert.ok( !step1.classList.contains( \"active\" ),\n                 step1.id + \" unset active css class.\" );\n\n    }, \"non-default-id\" ); // InitPresentation()\n  } ); // LoadIframe()\n} );\n"
  },
  {
    "path": "test/plugins/rel/padding_tests.js",
    "content": "QUnit.module( \"rel plugin padding tests\" );\n\nQUnit.test( \"padding_relative\", function( assert ) {\n  window.console.log( \"Begin padding_relative\" );\n  var done = assert.async();\n\n  loadIframe( \"test/plugins/rel/padding_tests_presentation.html\", assert, function() {\n    initPresentation( assert, function() {\n      var iframe = document.getElementById( \"presentation-iframe\" );\n      var iframeDoc = iframe.contentDocument;\n\n      var root  = iframeDoc.querySelector( \"div#impress\" );\n\n      var origin = iframeDoc.querySelector( \"div#origin\" );\n      var origin_x = iframeDoc.querySelector( \"div#origin_x\" );\n      var origin_y = iframeDoc.querySelector( \"div#origin_y\" );\n      var origin_z = iframeDoc.querySelector( \"div#origin_z\" );\n\n      var x = iframeDoc.querySelector( \"div#x\" );\n      var x_x = iframeDoc.querySelector( \"div#x_x\" );\n      var x_y = iframeDoc.querySelector( \"div#x_y\" );\n      var x_z = iframeDoc.querySelector( \"div#x_z\" );\n\n      var y = iframeDoc.querySelector( \"div#y\" );\n      var y_x = iframeDoc.querySelector( \"div#y_x\" );\n      var y_y = iframeDoc.querySelector( \"div#y_y\" );\n      var y_z = iframeDoc.querySelector( \"div#y_z\" );\n\n      var z = iframeDoc.querySelector( \"div#z\" );\n      var z_x = iframeDoc.querySelector( \"div#z_x\" );\n      var z_y = iframeDoc.querySelector( \"div#z_y\" );\n      var z_z = iframeDoc.querySelector( \"div#z_z\" );\n\n      var reset = iframeDoc.querySelector( \"div#reset\" );\n\n      assert.close( origin.dataset.x, 0, 1, \"origin data-x attribute\" );\n      assert.close( origin.dataset.y, 0, 1, \"origin data-y attribute\" );\n      assert.close( origin.dataset.z, 0, 1, \"origin data-z attribute\" );\n      assert.close( origin.dataset.rotateX, 0, 1, \"origin data-rotate-x\" );\n      assert.close( origin.dataset.rotateY, 0, 1, \"origin data-rotate-y\" );\n      assert.close( origin.dataset.rotateZ, 0, 1, \"origin data-rotate-z\" );\n\n      assert.close( origin_x.dataset.x, 500, 1, \"origin_x data-x attribute\" );\n      assert.close( origin_x.dataset.y, 0, 1, \"origin_x data-y attribute\" );\n      assert.close( origin_x.dataset.z, 0, 1, \"origin_x data-z attribute\" );\n      assert.close( origin_x.dataset.rotateX, 0, 1, \"origin_x data-rotate-x\" );\n      assert.close( origin_x.dataset.rotateY, 0, 1, \"origin_x data-rotate-y\" );\n      assert.close( origin_x.dataset.rotateZ, 0, 1, \"origin_x data-rotate-z\" );\n\n      assert.close( origin_y.dataset.x, 0, 1, \"origin_y data-x attribute\" );\n      assert.close( origin_y.dataset.y, 500, 1, \"origin_y data-y attribute\" );\n      assert.close( origin_y.dataset.z, 0, 1, \"origin_y data-z attribute\" );\n      assert.close( origin_y.dataset.rotateX, 0, 1, \"origin_y data-rotate-x\" );\n      assert.close( origin_y.dataset.rotateY, 0, 1, \"origin_y data-rotate-y\" );\n      assert.close( origin_y.dataset.rotateZ, 0, 1, \"origin_y data-rotate-z\" );\n\n      assert.close( origin_z.dataset.x, 0, 1, \"origin_z data-x attribute\" );\n      assert.close( origin_z.dataset.y, 0, 1, \"origin_z data-y attribute\" );\n      assert.close( origin_z.dataset.z, 500, 1, \"origin_z data-z attribute\" );\n      assert.close( origin_z.dataset.rotateX, 0, 1, \"origin_z data-rotate-x\" );\n      assert.close( origin_z.dataset.rotateY, 0, 1, \"origin_z data-rotate-y\" );\n      assert.close( origin_z.dataset.rotateZ, 0, 1, \"origin_z data-rotate-z\" );\n\n      assert.close( x.dataset.x, 0, 1, \"x data-x attribute\" );\n      assert.close( x.dataset.y, 0, 1, \"x data-y attribute\" );\n      assert.close( x.dataset.z, 0, 1, \"x data-z attribute\" );\n      assert.close( x.dataset.rotateX, 90, 1, \"x data-rotate-x\" );\n      assert.close( x.dataset.rotateY, 0, 1, \"x data-rotate-y\" );\n      assert.close( x.dataset.rotateZ, 0, 1, \"x data-rotate-z\" );\n\n      assert.close( x_x.dataset.x, 500, 1, \"x_x data-x attribute\" );\n      assert.close( x_x.dataset.y, 0, 1, \"x_x data-y attribute\" );\n      assert.close( x_x.dataset.z, 0, 1, \"x_x data-z attribute\" );\n      assert.close( x_x.dataset.rotateX, 90, 1, \"x_x data-rotate-x\" );\n      assert.close( x_x.dataset.rotateY, 0, 1, \"x_x data-rotate-y\" );\n      assert.close( x_x.dataset.rotateZ, 0, 1, \"x_x data-rotate-z\" );\n\n      assert.close( x_y.dataset.x, 0, 1, \"x_y data-x attribute\" );\n      assert.close( x_y.dataset.y, 0, 1, \"x_y data-y attribute\" );\n      assert.close( x_y.dataset.z, 500, 1, \"x_y data-z attribute\" );\n      assert.close( x_y.dataset.rotateX, 90, 1, \"x_y data-rotate-x\" );\n      assert.close( x_y.dataset.rotateY, 0, 1, \"x_y data-rotate-y\" );\n      assert.close( x_y.dataset.rotateZ, 0, 1, \"x_y data-rotate-z\" );\n\n      assert.close( x_z.dataset.x, 0, 1, \"x_z data-x attribute\" );\n      assert.close( x_z.dataset.y, -500, 1, \"x_z data-y attribute\" );\n      assert.close( x_z.dataset.z, 0, 1, \"x_z data-z attribute\" );\n      assert.close( x_z.dataset.rotateX, 90, 1, \"x_z data-rotate-x\" );\n      assert.close( x_z.dataset.rotateY, 0, 1, \"x_z data-rotate-y\" );\n      assert.close( x_z.dataset.rotateZ, 0, 1, \"x_z data-rotate-z\" );\n\n      assert.close( y.dataset.x, 0, 1, \"y data-x attribute\" );\n      assert.close( y.dataset.y, 0, 1, \"y data-y attribute\" );\n      assert.close( y.dataset.z, 0, 1, \"y data-z attribute\" );\n      assert.close( y.dataset.rotateX, 0, 1, \"y data-rotate-x\" );\n      assert.close( y.dataset.rotateY, 90, 1, \"y data-rotate-y\" );\n      assert.close( y.dataset.rotateZ, 0, 1, \"y data-rotate-z\" );\n\n      assert.close( y_x.dataset.x, 0, 1, \"y_x data-x attribute\" );\n      assert.close( y_x.dataset.y, 0, 1, \"y_x data-y attribute\" );\n      assert.close( y_x.dataset.z, -500, 1, \"y_x data-z attribute\" );\n      assert.close( y_x.dataset.rotateX, 0, 1, \"y_x data-rotate-x\" );\n      assert.close( y_x.dataset.rotateY, 90, 1, \"y_x data-rotate-y\" );\n      assert.close( y_x.dataset.rotateZ, 0, 1, \"y_x data-rotate-z\" );\n\n      assert.close( y_y.dataset.x, 0, 1, \"y_y data-x attribute\" );\n      assert.close( y_y.dataset.y, 500, 1, \"y_y data-y attribute\" );\n      assert.close( y_y.dataset.z, 0, 1, \"y_y data-z attribute\" );\n      assert.close( y_y.dataset.rotateX, 0, 1, \"y_y data-rotate-x\" );\n      assert.close( y_y.dataset.rotateY, 90, 1, \"y_y data-rotate-y\" );\n      assert.close( y_y.dataset.rotateZ, 0, 1, \"y_y data-rotate-z\" );\n\n      assert.close( y_z.dataset.x, 500, 1, \"y_z data-x attribute\" );\n      assert.close( y_z.dataset.y, 0, 1, \"y_z data-y attribute\" );\n      assert.close( y_z.dataset.z, 0, 1, \"y_z data-z attribute\" );\n      assert.close( y_z.dataset.rotateX, 0, 1, \"y_z data-rotate-x\" );\n      assert.close( y_z.dataset.rotateY, 90, 1, \"y_z data-rotate-y\" );\n      assert.close( y_z.dataset.rotateZ, 0, 1, \"y_z data-rotate-z\" );\n\n      assert.close( z.dataset.x, 0, 1, \"z data-x attribute\" );\n      assert.close( z.dataset.y, 0, 1, \"z data-y attribute\" );\n      assert.close( z.dataset.z, 0, 1, \"z data-z attribute\" );\n      assert.close( z.dataset.rotateX, 0, 1, \"z data-rotate-x\" );\n      assert.close( z.dataset.rotateY, 0, 1, \"z data-rotate-y\" );\n      assert.close( z.dataset.rotateZ, 90, 1, \"z data-rotate-z\" );\n\n      assert.close( z_x.dataset.x, 0, 1, \"z_x data-x attribute\" );\n      assert.close( z_x.dataset.y, 500, 1, \"z_x data-y attribute\" );\n      assert.close( z_x.dataset.z, 0, 1, \"z_x data-z attribute\" );\n      assert.close( z_x.dataset.rotateX, 0, 1, \"z_x data-rotate-x\" );\n      assert.close( z_x.dataset.rotateY, 0, 1, \"z_x data-rotate-y\" );\n      assert.close( z_x.dataset.rotateZ, 90, 1, \"z_x data-rotate-z\" );\n\n      assert.close( z_y.dataset.x, -500, 1, \"z_y data-x attribute\" );\n      assert.close( z_y.dataset.y, 0, 1, \"z_y data-y attribute\" );\n      assert.close( z_y.dataset.z, 0, 1, \"z_y data-z attribute\" );\n      assert.close( z_y.dataset.rotateX, 0, 1, \"z_y data-rotate-x\" );\n      assert.close( z_y.dataset.rotateY, 0, 1, \"z_y data-rotate-y\" );\n      assert.close( z_y.dataset.rotateZ, 90, 1, \"z_y data-rotate-z\" );\n\n      assert.close( z_z.dataset.x, 0, 1, \"z_z data-x attribute\" );\n      assert.close( z_z.dataset.y, 0, 1, \"z_z data-y attribute\" );\n      assert.close( z_z.dataset.z, 500, 1, \"z_z data-z attribute\" );\n      assert.close( z_z.dataset.rotateX, 0, 1, \"z_z data-rotate-x\" );\n      assert.close( z_z.dataset.rotateY, 0, 1, \"z_z data-rotate-y\" );\n      assert.close( z_z.dataset.rotateZ, 90, 1, \"z_z data-rotate-z\" );\n\n      assert.close( reset.dataset.x, 100, 1, \"reset data-x attribute\" );\n      assert.close( reset.dataset.y, 200, 1, \"reset data-y attribute\" );\n      assert.close( reset.dataset.z, 800, 1, \"reset data-z attribute\" );\n      assert.close( reset.dataset.rotateX, 0, 1, \"reset data-rotate-x\" );\n      assert.close( reset.dataset.rotateY, 0, 1, \"reset data-rotate-y\" );\n      assert.close( reset.dataset.rotateZ, 0, 1, \"reset data-rotate-z\" );\n\n      done();\n      console.log( \"End padding test (sync)\" );\n    } )\n  } ); // LoadIframe()\n} );\n\n"
  },
  {
    "path": "test/plugins/rel/padding_tests_presentation.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>The presentation steps used in an iframe in rel/padding_tests.js</title>\n    <style type=\"text/css\" media=\"screen\">\n.step {\n    position: relative;\n    width: 500px;\n    height: 500px;\n    padding: 40px 60px;\n    margin: 20px auto;\n\n    box-sizing:         border-box;\n\n    line-height: 1.5;\n\n    background-color: yellow;\n    border-radius: 10px;\n    box-shadow: 0 2px 6px rgba(0, 0, 0, .1);\n\n    text-shadow: 0 2px 2px rgba(0, 0, 0, .1);\n\n    font-family: 'Open Sans', Arial, sans-serif;\n    font-size: 40pt;\n    letter-spacing: -1px;\n    border: solid 1px red;\n    opacity: 50%;\n}\n    </style>\n  </head>\n\n  <body class=\"impress-not-supported\">\n    <div id=\"impress\">\n      <div id=\"origin\"   class=\"step\" data-rel-position=\"relative\" data-x=\"0\" data-y=\"0\" data-z=\"0\"></div>\n      <div id=\"origin_x\" class=\"step\" data-rel-to=\"origin\" data-rel-reset data-rel-x=\"500\"></div>\n      <div id=\"origin_y\" class=\"step\" data-rel-to=\"origin\" data-rel-reset data-rel-y=\"500\"></div>\n      <div id=\"origin_z\" class=\"step\" data-rel-to=\"origin\" data-rel-reset data-rel-z=\"500\"></div>\n\n      <div id=\"x\" class=\"step\" data-rel-reset=\"all\" data-x=\"0\" data-y=\"0\" data-z=\"0\" data-rotate-x=\"90\"></div>\n      <div id=\"x_x\" class=\"step\" data-rel-reset data-rel-to=\"x\" data-rel-x=\"500\"></div>\n      <div id=\"x_y\" class=\"step\" data-rel-reset data-rel-to=\"x\" data-rel-y=\"500\"></div>\n      <div id=\"x_z\" class=\"step\" data-rel-reset data-rel-to=\"x\" data-rel-z=\"500\"></div>\n\n      <div id=\"y\" class=\"step\" data-rel-reset=\"all\" data-x=\"0\" data-y=\"0\" data-z=\"0\" data-rotate-y=\"90\"></div>\n      <div id=\"y_x\" class=\"step\" data-rel-reset data-rel-to=\"y\" data-rel-x=\"500\"></div>\n      <div id=\"y_y\" class=\"step\" data-rel-reset data-rel-to=\"y\" data-rel-y=\"500\"></div>\n      <div id=\"y_z\" class=\"step\" data-rel-reset data-rel-to=\"y\" data-rel-z=\"500\"></div>\n\n      <div id=\"z\" class=\"step\" data-rel-reset=\"all\" data-x=\"0\" data-y=\"0\" data-z=\"0\" data-rotate-z=\"90\"></div>\n      <div id=\"z_x\" class=\"step\" data-rel-reset data-rel-to=\"z\" data-rel-x=\"500\"></div>\n      <div id=\"z_y\" class=\"step\" data-rel-reset data-rel-to=\"z\" data-rel-y=\"500\"></div>\n      <div id=\"z_z\" class=\"step\" data-rel-reset data-rel-to=\"z\" data-rel-z=\"500\"></div>\n\n      <div id=\"reset\" class=\"step\" data-rel-reset=\"all\" data-rel-x=\"100\" data-rel-y=\"200\" data-rel-z=\"300\">\n          by data prel-reset=\"all\", paddings should be calculated as if data-rotate-*=0\n      </div>\n\n      <div id=\"overview\" class=\"step overview\" data-x=\"0\" data-y=\"-1000\" data-z=\"100\" data-scale=\"4\" data-rotate-x=\"45\">\n      </div>\n\n    </div>\n\n    <script src=\"../../../js/impress.js\"></script>\n    <!-- <script>impress().init();</script> -->\n  </body>\n<html>\n"
  },
  {
    "path": "test/plugins/rel/rel_to_tests.js",
    "content": "QUnit.module( \"rel plugin rel_to tests\" );\n\nQUnit.test( \"rel_to\", function( assert ) {\n    window.console.log( \"Begin rel_to\" );\n    var done = assert.async();\n\n    loadIframe( \"test/plugins/rel/rel_to_tests_presentation.html\", assert, function() {\n        initPresentation( assert, function() {\n            var iframe = document.getElementById( \"presentation-iframe\" );\n            var iframeDoc = iframe.contentDocument;\n\n            var root  = iframeDoc.querySelector( \"div#impress\" );\n            var abs1 = iframeDoc.querySelector( \"div#abs-1\" );\n            var abs2 = iframeDoc.querySelector( \"div#abs-2\" );\n            var abs3 = iframeDoc.querySelector( \"div#abs-3\" );\n            var abs4 = iframeDoc.querySelector( \"div#abs-4\" );\n            var abs5 = iframeDoc.querySelector( \"div#abs-5\" );\n            var abs6 = iframeDoc.querySelector( \"div#abs-6\" );\n            var relative1 = iframeDoc.querySelector( \"div#relative-1\" );\n            var relative2 = iframeDoc.querySelector( \"div#relative-2\" );\n            var relative3 = iframeDoc.querySelector( \"div#relative-3\" );\n            var relative4 = iframeDoc.querySelector( \"div#relative-4\" );\n            var relative5 = iframeDoc.querySelector( \"div#relative-5\" );\n            var relative6 = iframeDoc.querySelector( \"div#relative-6\" );\n            var relative7 = iframeDoc.querySelector( \"div#relative-7\" );\n            var relative8 = iframeDoc.querySelector( \"div#relative-8\" );\n            var relative9 = iframeDoc.querySelector( \"div#relative-9\" );\n            var relative10 = iframeDoc.querySelector( \"div#relative-10\" );\n\n            assert.close( abs1.dataset.x, 0, 1, \"abs-1 data-x attribute\" );\n            assert.close( abs1.dataset.y, 0, 1, \"abs-1 data-y attribute\" );\n            assert.close( abs1.dataset.z, 0, 1, \"abs-1 data-z attribute\" );\n            assert.close( abs1.dataset.rotateX, 0, 1, \"abs-1 data-rotate-x\" );\n            assert.close( abs1.dataset.rotateY, 0, 1, \"abs-1 data-rotate-y\" );\n            assert.close( abs1.dataset.rotateZ, 0, 1, \"abs-1 data-rotate-z\" );\n\n            assert.close( abs2.dataset.x, 854, 1, \"abs-2 data-x attribute\" );\n            assert.close( abs2.dataset.y, -125, 1, \"abs-2 data-y attribute\" );\n            assert.close( abs2.dataset.z, -354, 1, \"abs-2 data-z attribute\" );\n            assert.close( abs2.dataset.rotateX, 0, 1, \"abs-2 data-rotate-x attribute\" );\n            assert.close( abs2.dataset.rotateY, 45, 1, \"abs-2 data-rotate-y attribute\" );\n            assert.close( abs2.dataset.rotateZ, 0, 1, \"abs-2 data-rotate-z attribute\" );\n\n            assert.close( abs3.dataset.x, 1708, 1, \"abs-3 data-x attribute\" );\n            assert.close( abs3.dataset.y, -250, 1, \"abs-3 data-y attribute\" );\n            assert.close( abs3.dataset.z, -708, 1, \"abs-3 data-z attribute\" );\n            assert.close( abs3.dataset.rotateX, 0, 1, \"abs-3 data-rotate-x attribute\" );\n            assert.close( abs3.dataset.rotateY, 45, 1, \"abs-3 data-rotate-y attribute\" );\n            assert.close( abs3.dataset.rotateZ, 0, 1, \"abs-3 data-rotate-z attribute\" );\n\n            assert.close( abs4.dataset.x, 1808, 1, \"abs-4 data-x attribute\" );\n            assert.close( abs4.dataset.y, -250, 1, \"abs-4 data-y attribute\" );\n            assert.close( abs4.dataset.z, -708, 1, \"abs-4 data-z attribute\" );\n            assert.close( abs4.dataset.rotateX, 0, 1, \"abs-4 data-rotate-x attribute\" );\n            assert.close( abs4.dataset.rotateY, 0, 1, \"abs-4 data-rotate-y attribute\" );\n            assert.close( abs4.dataset.rotateZ, 0, 1, \"abs-4 data-rotate-z attribute\" );\n\n            assert.close( abs5.dataset.x, 1708, 1, \"abs-5 data-x attribute\" );\n            assert.close( abs5.dataset.y, -250, 1, \"abs-5 data-y attribute\" );\n            assert.close( abs5.dataset.z, -708, 1, \"abs-5 data-z attribute\" );\n            assert.close( abs5.dataset.rotateX, 0, 1, \"abs-5 data-rotate-x attribute\" );\n            assert.close( abs5.dataset.rotateY, 0, 1, \"abs-5 data-rotate-y attribute\" );\n            assert.close( abs5.dataset.rotateZ, 0, 1, \"abs-5 data-rotate-z attribute\" );\n\n            assert.close( abs6.dataset.x, 1708, 1, \"abs-6 data-x attribute\" );\n            assert.close( abs6.dataset.y, -250, 1, \"abs-6 data-y attribute\" );\n            assert.close( abs6.dataset.z, -708, 1, \"abs-6 data-z attribute\" );\n            assert.close( abs6.dataset.rotateX, 0, 1, \"abs-6 data-rotate-x attribute\" );\n            assert.close( abs6.dataset.rotateY, 0, 1, \"abs-6 data-rotate-y attribute\" );\n            assert.close( abs6.dataset.rotateZ, 0, 1, \"abs-6 data-rotate-z attribute\" );\n\n            assert.close( relative1.dataset.x, 0, 1, \"relative-1 data-x attribute\" );\n            assert.close( relative1.dataset.y, 1000, 1, \"relative-1 data-y attribute\" );\n            assert.close( relative1.dataset.z, 0, 1, \"relative-1 data-z attribute\" );\n            assert.close( relative1.dataset.rotateX, 0, 1, \"relative-1 data-rotate-x\" );\n            assert.close( relative1.dataset.rotateY, 0, 1, \"relative-1 data-rotate-y\" );\n            assert.close( relative1.dataset.rotateZ, 0, 1, \"relative-1 data-rotate-z\" );\n\n            assert.close( relative2.dataset.x, 854, 1, \"relative-2 data-x attribute\" );\n            assert.close( relative2.dataset.y, 875, 1, \"relative-2 data-y attribute\" );\n            assert.close( relative2.dataset.z, -354, 1, \"relative-2 data-z attribute\" );\n            assert.close( relative2.dataset.rotateX, 0, 1, \"relative-2 data-rotate-x attribute\" );\n            assert.close( relative2.dataset.rotateY, 45, 1, \"relative-2 data-rotate-y attribute\" );\n            assert.close( relative2.dataset.rotateZ, 0, 1, \"relative-2 data-rotate-z attribute\" );\n\n            assert.close( relative3.dataset.x, 1208, 1, \"relative-3 data-x attribute\" );\n            assert.close( relative3.dataset.y, 750, 1, \"relative-3 data-y attribute\" );\n            assert.close( relative3.dataset.z, -1208, 1, \"relative-3 data-z attribute\" );\n            assert.close( relative3.dataset.rotateX, 0, 1, \"relative-3 data-rotate-x attribute\" );\n            assert.close( relative3.dataset.rotateY, 90, 1, \"relative-3 data-rotate-y attribute\" );\n            assert.close( relative3.dataset.rotateZ, 0, 1, \"relative-3 data-rotate-z attribute\" );\n\n            assert.close( relative4.dataset.x, 1308, 1, \"relative-4 data-x attribute\" );\n            assert.close( relative4.dataset.y, 750, 1, \"relative-4 data-y attribute\" );\n            assert.close( relative4.dataset.z, -1208, 1, \"relative-4 data-z attribute\" );\n            assert.close( relative4.dataset.rotateX, 0, 1, \"relative-4 data-rotate-x attribute\" );\n            assert.close( relative4.dataset.rotateY, 0, 1, \"relative-4 data-rotate-y attribute\" );\n            assert.close( relative4.dataset.rotateZ, 0, 1, \"relative-4 data-rotate-z attribute\" );\n\n            assert.close( relative5.dataset.x, 854, 1, \"relative-5 data-x attribute\" );\n            assert.close( relative5.dataset.y, 625, 1, \"relative-5 data-y attribute\" );\n            assert.close( relative5.dataset.z, -2062, 1, \"relative-5 data-z attribute\" );\n            assert.close( relative5.dataset.rotateX, 0, 1, \"relative-5 data-rotate-x\" );\n            assert.close( relative5.dataset.rotateY, 135, 1, \"relative-5 data-rotate-y\" );\n            assert.close( relative5.dataset.rotateZ, 0, 1, \"relative-5 data-rotate-z\" );\n\n            assert.close( relative6.dataset.x, 0, 1, \"relative-6 data-x attribute\" );\n            assert.close( relative6.dataset.y, 500, 1, \"relative-6 data-y attribute\" );\n            assert.close( relative6.dataset.z, -2416, 1, \"relative-6 data-z attribute\" );\n            assert.close( relative6.dataset.rotateX, 0, 1, \"relative-6 data-rotate-x\" );\n            assert.close( relative6.dataset.rotateY, 180, 1, \"relative-6 data-rotate-y\" );\n            assert.close( relative6.dataset.rotateZ, 0, 1, \"relative-6 data-rotate-z\" );\n\n            assert.close( relative7.dataset.x, 1208, 1, \"relative-7 data-x attribute\" );\n            assert.close( relative7.dataset.y, 750, 1, \"relative-7 data-y attribute\" );\n            assert.close( relative7.dataset.z, -1208, 1, \"relative-7 data-z attribute\" );\n            assert.close( relative7.dataset.rotateX, 0, 1, \"relative-7 data-rotate-x\" );\n            assert.close( relative7.dataset.rotateY, 90, 1, \"relative-7 data-rotate-y\" );\n            assert.close( relative7.dataset.rotateZ, 0, 1, \"relative-7 data-rotate-z\" );\n\n            assert.close( relative8.dataset.x, 1208, 1, \"relative-8 data-x attribute\" );\n            assert.close( relative8.dataset.y, 750, 1, \"relative-8 data-y attribute\" );\n            assert.close( relative8.dataset.z, -1208, 1, \"relative-8 data-z attribute\" );\n            assert.close( relative8.dataset.rotateX, 0, 1, \"relative-8 data-rotate-x\" );\n            assert.close( relative8.dataset.rotateY, 90, 1, \"relative-8 data-rotate-y\" );\n            assert.close( relative8.dataset.rotateZ, 0, 1, \"relative-8 data-rotate-z\" );\n\n            assert.close( relative9.dataset.x, 1208, 1, \"relative-9 data-x attribute\" );\n            assert.close( relative9.dataset.y, 750, 1, \"relative-9 data-y attribute\" );\n            assert.close( relative9.dataset.z, -1208, 1, \"relative-9 data-z attribute\" );\n            assert.close( relative9.dataset.rotateX, 0, 1, \"relative-9 data-rotate-x\" );\n            assert.close( relative9.dataset.rotateY, 0, 1, \"relative-9 data-rotate-y\" );\n            assert.close( relative9.dataset.rotateZ, 0, 1, \"relative-9 data-rotate-z\" );\n\n            assert.close( relative10.dataset.x, 1208, 1, \"relative-10 data-x attribute\" );\n            assert.close( relative10.dataset.y, 750, 1, \"relative-10 data-y attribute\" );\n            assert.close( relative10.dataset.z, -1208, 1, \"relative-10 data-z attribute\" );\n            assert.close( relative10.dataset.rotateX, 0, 1, \"relative-10 data-rotate-x\" );\n            assert.close( relative10.dataset.rotateY, 0, 1, \"relative-10 data-rotate-y\" );\n            assert.close( relative10.dataset.rotateZ, 0, 1, \"relative-10 data-rotate-z\" );\n\n            done();\n            console.log( \"End rel_to test (sync)\" );\n        } )\n    } ); // LoadIframe()\n} );\n\n"
  },
  {
    "path": "test/plugins/rel/rel_to_tests_presentation.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>The presentation steps used in an iframe in rel/rotation_tests.js</title>\n    <style type=\"text/css\" media=\"screen\">\n.step {\n    position: relative;\n    width: 1000px;\n    height: 1000px;\n    padding: 40px 60px;\n    margin: 20px auto;\n\n    box-sizing:         border-box;\n\n    line-height: 1.5;\n\n    background-color: none;\n    border-radius: 10px;\n    box-shadow: 0 2px 6px rgba(0, 0, 0, .1);\n\n    text-shadow: 0 2px 2px rgba(0, 0, 0, .1);\n\n    font-family: 'Open Sans', Arial, sans-serif;\n    font-size: 40pt;\n    letter-spacing: -1px;\n    border: solid 1px red;\n}\n    </style>\n  </head>\n\n  <body class=\"impress-not-supported\">\n    <div id=\"impress\" data-width=\"1920\" data-height=\"1200\">\n      <div id=\"overview\" class=\"step overview\" data-x=\"0\" data-y=\"-1000\" data-z=\"100\" data-scale=\"4\" data-rotate-x=\"45\">\n        <h1>Testing related to <code>data-rel-to</code></h1>\n      </div>\n\n      <div id=\"abs-1\" class=\"step\" data-x=\"0\" data-y=\"0\" data-z=\"0\">\n        <p>Testing default rel-position</p>\n      </div>\n\n      <div id=\"abs-2\" class=\"step\" data-rel-rotate-y=\"45\" data-rel-z=\"-354\" data-rel-x=\"854\" data-rel-y=\"-125\">\n        <p>default rel-position, rel-rotate not work</p>\n      </div>\n\n      <div id=\"abs-3\" class=\"step\">\n        <p>Should inherit rel-x/y/z, not rel-rotate-y</p>\n      </div>\n\n      <div id=\"abs-4\" class=\"step\" data-rel-reset data-rel-x=\"100\">\n        <p>position out of flow</p>\n      </div>\n\n      <div id=\"abs-5\" class=\"step\" data-rel-to=\"abs-3\">\n        <p>Should re-position at abs-3, but doesn't inherit from it</p>\n      </div>\n\n      <div id=\"abs-6\" class=\"step\">\n        <p>Should be the same as abs-5</p>\n      </div>\n\n      <div id=\"relative-1\" class=\"step\" data-rel-reset=\"all\" data-rel-position=\"relative\" data-x=\"0\" data-y=\"1000\" data-z=\"0\">\n        <p>Testing rel-position=\"relative\"</p>\n      </div>\n\n      <div id=\"relative-2\" class=\"step\" data-rel-rotate-y=\"45\" data-rel-z=\"-354\" data-rel-x=\"854\" data-rel-y=\"-125\">\n        <p>All rel-* will be inherited from previous slide</p>\n      </div>\n\n      <div id=\"relative-3\" class=\"step\">\n        <p>inherit again</p>\n      </div>\n\n      <div id=\"relative-4\" class=\"step\" data-rel-reset=\"all\" data-rel-x=\"100\">\n        <p>position out of flow</p>\n      </div>\n\n      <div id=\"relative-5\" class=\"step\" data-rel-to=\"relative-3\">\n        <p>Should re-position at relative-3, and inherit all relatives from it</p>\n      </div>\n\n      <div id=\"relative-6\" class=\"step\">\n        <p>keep inheriting</p>\n      </div>\n\n      <div id=\"relative-7\" class=\"step\" data-rel-to=\"relative-3\" data-rel-reset>\n        <p>Should re-position at relative-3, but clear all relative attributes</p>\n      </div>\n\n      <div id=\"relative-8\" class=\"step\">\n        <p>keep inheriting</p>\n      </div>\n\n      <div id=\"relative-9\" class=\"step\" data-rel-to=\"relative-3\" data-rel-reset=\"all\">\n        <p>Should re-position at relative-3, but clear data-rotate-* in addition to all relative attributes</p>\n      </div>\n\n      <div id=\"relative-10\" class=\"step\">\n        <p>keep inheriting</p>\n      </div>\n    </div>\n\n    <script src=\"../../../js/impress.js\"></script>\n    <!-- <script>impress().init();</script> -->\n  </body>\n<html>\n"
  },
  {
    "path": "test/plugins/rel/relative_to_screen_size_tests.js",
    "content": "QUnit.module( \"rel plugin relative to screen size tests\" );\n\nQUnit.test( \"relative_to_screen_size\", function( assert ) {\n  window.console.log( \"Begin relative_to_screen_size\" );\n  var done = assert.async();\n\n  loadIframe( \"test/plugins/rel/relative_to_screen_size_tests_presentation.html\", assert, function() {\n    initPresentation( assert, function() {\n      var iframe = document.getElementById( \"presentation-iframe\" );\n      var iframeDoc = iframe.contentDocument;\n\n      var root  = iframeDoc.querySelector( \"div#impress\" );\n\n      var origin = iframeDoc.querySelector( \"div#origin\" );\n      var step1 = iframeDoc.querySelector( \"div#step1\" );\n      var step2 = iframeDoc.querySelector( \"div#step2\" );\n      var step3 = iframeDoc.querySelector( \"div#step3\" );\n      var overview = iframeDoc.querySelector( \"div#overview\" );\n\n      assert.equal( origin.dataset.x, 0, \"origin data-x attribute\" );\n      assert.equal( origin.dataset.y, 0, \"origin data-y attribute\" );\n      assert.equal( origin.dataset.z, 0, \"origin data-z attribute\" );\n\n      assert.equal( step1.dataset.x, 2000, \"step1 data-x attribute\" );\n      assert.equal( step1.dataset.y, 1500, \"step1 data-y attribute\" );\n      assert.equal( step1.dataset.z, -2000, \"step1 data-z attribute\" );\n\n      assert.equal( step2.dataset.x, -3000, \"step2 data-x attribute\" );\n      assert.equal( step2.dataset.y, -4000, \"step2 data-y attribute\" );\n      assert.equal( step2.dataset.z, 3000, \"step2 data-z attribute\" );\n\n      assert.equal( step3.dataset.x, 1000, \"step3 data-x attribute\" );\n      assert.equal( step3.dataset.y, -750, \"step3 data-y attribute\" );\n      assert.equal( step3.dataset.z, 1000, \"step3 data-z attribute\" );\n\n      assert.equal( overview.dataset.x, 2000,  \"overview data-x attribute\" );\n      assert.equal( overview.dataset.y, -1500, \"overview data-y attribute\" );\n      assert.equal( overview.dataset.z, 1500,  \"overview data-z attribute\" );\n\n      done();\n      console.log( \"End relative_to_screen_size test (sync)\" );\n    } )\n  } ); // LoadIframe()\n} );\n\n"
  },
  {
    "path": "test/plugins/rel/relative_to_screen_size_tests_presentation.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>The presentation steps used in an iframe in rel/relative_to_screen_size_tests.js</title>\n    <style type=\"text/css\" media=\"screen\">\n.step {\n    position: relative;\n    width: 500px;\n    height: 500px;\n    padding: 40px 60px;\n    margin: 20px auto;\n\n    box-sizing:         border-box;\n\n    line-height: 1.5;\n\n    background-color: yellow;\n    border-radius: 10px;\n    box-shadow: 0 2px 6px rgba(0, 0, 0, .1);\n\n    text-shadow: 0 2px 2px rgba(0, 0, 0, .1);\n\n    font-family: 'Open Sans', Arial, sans-serif;\n    font-size: 40pt;\n    letter-spacing: -1px;\n    border: solid 1px red;\n    opacity: 50%;\n}\n    </style>\n  </head>\n\n  <body class=\"impress-not-supported\">\n    <div id=\"impress\" data-width=\"2000\" data-height=\"1500\">\n      <div id=\"origin\"   class=\"step\" data-rel-position=\"relative\" data-x=\"0\" data-y=\"0\" data-z=\"0\"></div>\n      <div id=\"step1\" class=\"step\" data-rel-to=\"origin\" data-rel-x=\"1w\" data-rel-y=\"1h\" data-rel-z=\"-1w\"></div>\n      <div id=\"step2\" class=\"step\" data-rel-to=\"origin\" data-rel-x=\"-2h\" data-rel-y=\"-2w\" data-rel-z=\"2h\"></div>\n      <div id=\"step3\" class=\"step\" data-rel-to=\"origin\" data-rel-x=\"0.5w\" data-rel-y=\"-0.5h\" data-rel-z=\"0.5w\"></div>\n\n      <div id=\"overview\" class=\"step overview\" data-x=\"1w\" data-y=\"-1h\" data-z=\"1h\" data-scale=\"4\" data-rotate-x=\"45\">\n      </div>\n\n    </div>\n\n    <script src=\"../../../js/impress.js\"></script>\n    <!-- <script>impress().init();</script> -->\n  </body>\n<html>\n"
  },
  {
    "path": "test/plugins/rel/rotation_tests.js",
    "content": "QUnit.module( \"rel plugin rotation tests\" );\n\nQUnit.test( \"rotation_relative\", function( assert ) {\n  window.console.log( \"Begin rotation_relative\" );\n  var done = assert.async();\n\n  loadIframe( \"test/plugins/rel/rotation_tests_presentation.html\", assert, function() {\n    initPresentation( assert, function() {\n      var iframe = document.getElementById( \"presentation-iframe\" );\n      var iframeDoc = iframe.contentDocument;\n\n      var root  = iframeDoc.querySelector( \"div#impress\" );\n      var step1 = iframeDoc.querySelector( \"div#step-1\" );\n      var step2 = iframeDoc.querySelector( \"div#step-2\" );\n      var step3 = iframeDoc.querySelector( \"div#step-3\" );\n      var step4 = iframeDoc.querySelector( \"div#step-4\" );\n      var step5 = iframeDoc.querySelector( \"div#step-5\" );\n      var step6 = iframeDoc.querySelector( \"div#step-6\" );\n      var step7 = iframeDoc.querySelector( \"div#step-7\" );\n      var step8 = iframeDoc.querySelector( \"div#step-8\" );\n      var reset = iframeDoc.querySelector( \"div#reset\" );\n      var rotate = iframeDoc.querySelector( \"div#rotate\" );\n\n      assert.close( step1.dataset.x, 0, 1, \"step-1 data-x attribute\" );\n      assert.close( step1.dataset.y, 0, 1, \"step-1 data-y attribute\" );\n      assert.close( step1.dataset.z, 0, 1, \"step-1 data-z attribute\" );\n      assert.close( step1.dataset.rotateX, 0, 1, \"step-1 data-rotate-x\" );\n      assert.close( step1.dataset.rotateY, 0, 1, \"step-1 data-rotate-y\" );\n      assert.close( step1.dataset.rotateZ, 0, 1, \"step-1 data-rotate-z\" );\n\n      assert.close( step2.dataset.x, 854, 1, \"step-2 data-x attribute\" );\n      assert.close( step2.dataset.y, -125, 1, \"step-2 data-y attribute\" );\n      assert.close( step2.dataset.z, -354, 1, \"step-2 data-z attribute\" );\n      assert.close( step2.dataset.rotateX, 0, 1, \"step-2 data-rotate-x attribute\" );\n      assert.close( step2.dataset.rotateY, 45, 1, \"step-2 data-rotate-y attribute\" );\n      assert.close( step2.dataset.rotateZ, 0, 1, \"step-2 data-rotate-z attribute\" );\n\n      assert.close( step3.dataset.x, 1208, 1, \"step-3 data-x attribute\" );\n      assert.close( step3.dataset.y, -250, 1, \"step-3 data-y attribute\" );\n      assert.close( step3.dataset.z, -1208, 1, \"step-3 data-z attribute\" );\n      assert.close( step3.dataset.rotateX, 0, 1, \"step-3 data-rotate-x attribute\" );\n      assert.close( step3.dataset.rotateY, 90, 1, \"step-3 data-rotate-y attribute\" );\n      assert.close( step3.dataset.rotateZ, 0, 1, \"step-3 data-rotate-z attribute\" );\n\n      assert.close( step4.dataset.x, 854, 1, \"step-4 data-x attribute\" );\n      assert.close( step4.dataset.y, -375, 1, \"step-4 data-y attribute\" );\n      assert.close( step4.dataset.z, -2062, 1, \"step-4 data-z attribute\" );\n      assert.close( step4.dataset.rotateX, 0, 1, \"step-4 data-rotate-x\" );\n      assert.close( step4.dataset.rotateY, 135, 1, \"step-4 data-rotate-y\" );\n      assert.close( step4.dataset.rotateZ, 0, 1, \"step-4 data-rotate-z\" );\n\n      assert.close( step5.dataset.x, 0, 1, \"step-5 data-x attribute\" );\n      assert.close( step5.dataset.y, -500, 1, \"step-5 data-y attribute\" );\n      assert.close( step5.dataset.z, -2416, 1, \"step-5 data-z attribute\" );\n      assert.close( step5.dataset.rotateX, 0, 1, \"step-5 data-rotate-x\" );\n      assert.close( step5.dataset.rotateY, 180, 1, \"step-5 data-rotate-y\" );\n      assert.close( step5.dataset.rotateZ, 0, 1, \"step-5 data-rotate-z\" );\n\n      assert.close( step6.dataset.x, -854, 1, \"step-6 data-x attribute\" );\n      assert.close( step6.dataset.y, -375, 1, \"step-6 data-y attribute\" );\n      assert.close( step6.dataset.z, -2062, 1, \"step-6 data-z attribute\" );\n      assert.close( step6.dataset.rotateX, 0, 1, \"step-6 data-rotate-x\" );\n      assert.close( step6.dataset.rotateY, 225, 1, \"step-6 data-rotate-y\" );\n      assert.close( step6.dataset.rotateZ, 0, 1, \"step-6 data-rotate-z\" );\n\n      assert.close( step7.dataset.x, -1208, 1, \"step-7 data-x attribute\" );\n      assert.close( step7.dataset.y, -250, 1, \"step-7 data-y attribute\" );\n      assert.close( step7.dataset.z, -1208, 1, \"step-7 data-z attribute\" );\n      assert.close( step7.dataset.rotateX, 0, 1, \"step-7 data-rotate-x attribute\" );\n      assert.close( step7.dataset.rotateY, 270, 1, \"step-7 data-rotate-y attribute\" );\n      assert.close( step7.dataset.rotateZ, 0, 1, \"step-7 data-rotate-z attribute\" );\n\n      assert.close( step8.dataset.x, -854, 1, \"step-8 data-x attribute\" );\n      assert.close( step8.dataset.y, -125, 1, \"step-8 data-y attribute\" );\n      assert.close( step8.dataset.z, -354, 1, \"step-8 data-z attribute\" );\n      assert.close( step8.dataset.rotateX, 0, 1, \"step-8 data-rotate-x attribute\" );\n      assert.close( step8.dataset.rotateY, 315, 1, \"step-8 data-rotate-y attribute\" );\n      assert.close( step8.dataset.rotateZ, 0, 1, \"step-8 data-rotate-z attribute\" );\n\n      assert.equal( reset.dataset.x, 0, \"reset data-x attribute\" );\n      assert.equal( reset.dataset.y, 0, \"reset data-y attribute\" );\n      assert.equal( reset.dataset.z, 0, \"reset data-z attribute\" );\n      assert.equal( reset.dataset.rotateX, 0, \"reset data-rotate-x\" );\n      assert.equal( reset.dataset.rotateY, 0, \"reset data-rotate-y\" );\n      assert.equal( reset.dataset.rotateZ, 0, \"reset data-rotate-z\" );\n      assert.equal( reset.dataset.rotateOrder, \"zyx\", \"reset data-rotate-order\" );\n\n      assert.equal( rotate.dataset.x, 0, \"rotate data-x attribute\" );\n      assert.equal( rotate.dataset.y, 0, \"rotate data-y attribute\" );\n      assert.equal( rotate.dataset.z, 0, \"rotate data-z attribute\" );\n      assert.equal( rotate.dataset.rotateX, 0, \"rotate data-rotate-x\" );\n      assert.equal( rotate.dataset.rotateY, 0, \"rotate data-rotate-y\" );\n      assert.equal( rotate.dataset.rotateZ, 123, \"rotate data-rotate-z\" );\n      assert.equal( rotate.dataset.rotateOrder, \"xyz\", \"rotate data-rotate-order\" );\n\n      done();\n      console.log( \"End rotation test (sync)\" );\n    } )\n  } ); // LoadIframe()\n} );\n\n"
  },
  {
    "path": "test/plugins/rel/rotation_tests_presentation.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>The presentation steps used in an iframe in rel/rotation_tests.js</title>\n    <style type=\"text/css\" media=\"screen\">\n.step {\n    position: relative;\n    width: 1000px;\n    height: 1000px;\n    padding: 40px 60px;\n    margin: 20px auto;\n\n    box-sizing:         border-box;\n\n    line-height: 1.5;\n\n    background-color: white;\n    border-radius: 10px;\n    box-shadow: 0 2px 6px rgba(0, 0, 0, .1);\n\n    text-shadow: 0 2px 2px rgba(0, 0, 0, .1);\n\n    font-family: 'Open Sans', Arial, sans-serif;\n    font-size: 40pt;\n    letter-spacing: -1px;\n    border: solid 1px red;\n}\n    </style>\n  </head>\n\n  <body class=\"impress-not-supported\">\n    <div id=\"impress\">\n      <div id=\"overview\" class=\"step overview\" data-x=\"0\" data-y=\"-1000\" data-z=\"100\" data-scale=\"4\" data-rotate-x=\"45\">\n        <h1>3D Rotations Demo</h1>\n      </div>\n\n      <div id=\"step-1\" class=\"step\" data-x=\"0\" data-y=\"0\" data-z=\"0\" data-goto-prev=\"step-8\">\n        <p>Slide one</p>\n      </div>\n\n      <div id=\"step-2\" class=\"step\" data-rel-position=\"relative\" data-rel-rotate-y=\"45\" data-rel-z=\"-354\" data-rel-x=\"854\" data-rel-y=\"-125\">\n        <p>Slide two</p>\n      </div>\n\n      <div id=\"step-3\" class=\"step\">\n        <p>Slide three</p>\n      </div>\n\n      <div id=\"step-4\" class=\"step\">\n        <p>Slide four</p>\n      </div>\n\n      <div id=\"step-5\" class=\"step\">\n        <p>Slide five</p>\n      </div>\n\n      <div id=\"step-6\" class=\"step\" data-rel-y=\"125\">\n        <p>Slide six</p>\n      </div>\n\n      <div id=\"step-7\" class=\"step\">\n        <p>Slide seven</p>\n      </div>\n\n      <div id=\"step-8\" class=\"step\" data-goto-next=\"step-1\">\n        <p>Slide eight</p>\n      </div>\n\n      <div id=\"reset\" class=\"step\" data-x=0 data-y=0 data-z=0 data-rotate-x=0 data-rotate-y=0 data-rotate-z=0 data-rotate-order=\"zyx\">\n        <p>Reset all properties</p>\n      </div>\n\n      <div id=\"rotate\" class=\"step\" data-rotate=123>\n        <p>Testing the data-rotate attribute</p>\n      </div>\n    </div>\n\n    <script src=\"../../../js/impress.js\"></script>\n    <!-- <script>impress().init();</script> -->\n  </body>\n<html>\n"
  }
]