[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 4\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[{.travis.yml,ci.yml}]\nindent_style = space\nindent_size = 2\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "\n<!--\n提issue，请注意：\n1. 请按照下面模板填写，否则issue会直接被关闭!\n2. 请使用Chrome浏览器查看样式问题，不再做其他浏览器兼容性支持;\n3. Node版本需要大于8.9，不再解决低版本问题;\n4. 请使用build命令直接导出静态文件部署，不鼓励使用seve命令做服务部署;\n5. 只有title的issue会被忽略\n--->\n\n\n\n\n| Executable | Version |\n| ---: | :--- |\n| `node --version` | VERSION |\n| `npm --version`  | VERSION |\n| `nodeppt -v` | VERSION |\n\n\n| OS | Version |\n| --- | --- |\n| NAME | VERSION |\n<!-- For example:\n| macOS Sierra | 10.12.3 |\n| Windows 10 | 1607 |\n| Ubuntu | 16.10 |\n-->\n"
  },
  {
    "path": ".gitignore",
    "content": "yarn.lock\n/node_modules\n/.sass-cache\n/src/index.html\n/test\n/3th\n/.idea\n/.vscode\n.DS_Store\n/npm-debug.log\ndist\npublish\noutput\n\nyarn-lock.json\npackage-lock.json\n\n.DS_Store\n### Node ###\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Typescript v1 declaration files\ntypings/\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n\n\n### WebStorm ###\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff:\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/dictionaries\n\n# Sensitive or high-churn files:\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.xml\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n\n# Gradle:\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# CMake\ncmake-build-debug/\n\n# Mongo Explorer plugin:\n.idea/**/mongoSettings.xml\n\n## File-based project format:\n*.iws\n\n## Plugin-specific files:\n\n# IntelliJ\n/out/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n### WebStorm Patch ###\n# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721\n\n# *.iml\n# modules.xml\n# .idea/misc.xml\n# *.ipr\n\n# Sonarlint plugin\n.idea/sonarlint\n\n# End of https://www.gitignore.io/api/node,webstorm\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n    \"singleQuote\": true,\n    \"printWidth\": 120,\n    \"tabWidth\": 4,\n    \"bracketSpacing\": false,\n    \"overrides\": [\n        {\n            \"files\": \".prettierrc\",\n            \"options\": {\"parser\": \"json\"}\n        }\n    ]\n}\n"
  },
  {
    "path": "MIT-LICENSE.txt",
    "content": "Copyright 2013 Theowang http://js8.in\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
  },
  {
    "path": "README.md",
    "content": "<h1 align=\"center\">nodeppt 2.0</h1>\n\n> 累死累活干不过做 PPT 的！<br/> > **查看效果：https://nodeppt.js.org**\n\n[![Markpress npm badge](https://nodei.co/npm/nodeppt.png)](https://www.npmjs.com/package/nodeppt)\n\n\n**nodeppt 2.0** 基于[webslides](https://github.com/webslides/WebSlides)、webpack、markdown-it、posthtml 重构，[新效果](https://nodeppt.js.org)\n\n<h2 align=\"center\">Install</h2>\n\n```bash\nnpm install -g nodeppt\n```\n\n## TODO\n* bug fix\n* 增加多页编辑公共资源，说人话就是 splitChunks\n\n<h2 align=\"center\">Usage</h2>\n\n简化了，就三个命令：\n\n-   new：使用线上模板创建一个新的 md 文件\n-   serve：启动一个 md 文件的 webpack dev server\n-   build：编译产出一个 md 文件\n\n```bash\n# create a new slide with an official template\n$ nodeppt new slide.md\n\n# create a new slide straight from a github template\n$ nodeppt new slide.md -t username/repo\n\n# start local sever show slide\n$ nodeppt serve slide.md\n\n# to build a slide\n$ nodeppt build slide.md\n```\n\n### 帮助\n\n```bash\n# help\nnodeppt -h\n# 获取帮助\nnodeppt serve -h\n```\n\n<h2 align=\"center\">演讲者模式</h2>\n\nnodeppt 有演讲者模式，在页面 url 后面增加`?mode=speaker` 既可以打开演讲者模式，双屏同步\n\n<h2 align=\"center\">Keyboard Shortcuts</h2>\n\n-   Page: ↑/↓/←/→ Space Home End\n-   Fullscreen: F\n-   Overview: -/+\n-   Speaker Note: N\n-   Grid Background: Enter\n\n<h2 align=\"center\">公共资源：public 文件夹</h2>\n\n如果项目文件夹下，存在`public`文件夹，可以直接通过 url 访问，参考`webpack dev server`的 `contentBase` 选项。\n\n在`build`的时候，public 文件夹中的文件会完全 copy 到`dist`文件夹中\n\n<h2 align=\"center\">编写</h2>\n\n最佳体验是 chrome 浏览器，本来就是给做演示用的，所以就别考虑非 Chrome 浏览器兼容问题了！\n\n这里说下怎么编写。\n\n### 基本语法\n\n整个 markdown 文件分为两部分，第一部分是写在最前面的**配置**，然后是使用`<slide>`隔开的每页幻灯片内容。\n\n### 配置\n\nnodeppt 的配置是直接写在 md 文件顶部的，采用 yaml 语法，例如下面配置：\n\n```yaml\ntitle: nodeppt markdown 演示\nspeaker: 三水清\nurl: https://github.com/ksky521/nodeppt\njs:\n    - https://www.echartsjs.com/asset/theme/shine.js\nprismTheme: solarizedlight\nplugins:\n    - echarts\n    - mermaid\n    - katex\n```\n\n-   title: 演讲主题\n-   speaker：演讲者\n-   url：地址\n-   js：js 文件数组，放到 body 之前\n-   css：css 文件数组，放到头部\n-   prismTheme：prism 配色，取值范围 `['dark', 'coy', 'funky', 'okaidia', 'tomorrow', 'solarizedlight', 'twilight']`\n-   plugins：目前支持 [echarts](https://echarts.baidu.com/)，[mermaid](https://mermaidjs.github.io/)和 [katex](https://katex.org) 三个插件\n\n#### 插件\n\n目前 nodeppt 支持 [图表 echarts](https://echarts.baidu.com/)，[流程图 mermaid](https://mermaidjs.github.io/)，[数学符号 KaTeX](https://katex.org) 三个插件。\n\n#### echarts\n\necharts 主题配色可以直接在`yaml`配置的 js 中引入。echarts 采用`fence`语法，如下：\n\n```echarts\n{\n    xAxis: {\n        type: 'category',\n        data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']\n    },\n    yAxis: {\n        type: 'value'\n    },\n    series: [{\n        data: [820, 932, 901, 934, 1290, 1330, 1320],\n        type: 'line'\n    }]\n}\n```\n详见[site/echarts.md](./site/echarts.md)\n\n#### mermaid\n\nmermaid 主题配色可以直接在`yaml`配置的 js 中引入。mermaid 采用`fence`语法，如下：\n\n```mermaid\nsequenceDiagram\n    Alice ->> Bob: Hello Bob, how are you?\n    Bob-->>John: How about you John?\n    Bob--x Alice: I am good thanks!\n    Bob-x John: I am good thanks!\n    Note right of John: Bob thinks a long<br/>long time, so long<br/>that the text does<br/>not fit on a row.\n\n    Bob-->Alice: Checking with John...\n    Alice->John: Yes... John, how are you?\n```\n\n详见[site/mermaid.md](./site/mermaid.md)\n#### ketex\n\n参考：[markdown-it-katex](https://www.npmjs.com/package/markdown-it-katex)语法\n\n### `<slide>` 语法\n\nnodeppt 会根据`<slide>`对整个 markdown 文件进行拆分，拆成单页的幻灯片内容。`<slide>` 标签支持下面标签：\n\n-   class/style 等：正常的 class 类，可以通过这个控制居中（aligncenter），内容位置，背景色等\n-   image：背景图片，基本语法 `image=\"img_url\"`\n-   video：背景视频，基本语法 `video=\"video_src1,video_src2\"`\n-   :class：wrap 的 class，下面详解\n\n每个 slide 会解析成下面的 html 结构：\n\n```html\n<section class=\"slide\" attrs...><div class=\"wrap\" wrap=\"true\">// 具体 markdown 渲染的内容</div></section>\n```\n\n其中`<slide>` 的`class`等会被解析到 `<section>`标签上面，而`:class`则被解析到`div.wrap`上面，例如：\n\n```html\n<slide :class=\"size-50\" class=\"bg-primary\"></slide>\n```\n\noutput 为：\n\n```html\n<section class=\"slide bg-primary\"><div class=\"wrap size-50\" wrap=\"true\">// 具体 markdown 渲染的内容</div></section>\n```\n\n#### 背景：图片\n\n`<slide>`的`image` 会被解析成背景大图，常见的支持方式有：\n\n```md\n<slide image=\"https://source.unsplash.com/UJbHNoVPZW0/\">\n\n# 这是一个普通的背景图\n\n<slide image=\"https://source.unsplash.com/UJbHNoVPZW0/ .dark\">\n\n# 这张背景图会在图片上面蒙一层偏黑色的透明层\n\n<slide image=\"https://source.unsplash.com/UJbHNoVPZW0/ .light\">\n\n# 这张背景图会在图片上面蒙一层偏白色的透明层\n\n<slide class=\"bg-black aligncenter\" image=\"https://source.unsplash.com/n9WPPWiPPJw/ .anim\">\n\n# 这张背景图会缓慢动\n```\n\n详见[site/background.md](./site/background.md)和[在线演示](https://js8.in/nodeppt/background.html)\n\n### 样式\n\n样式太多，具体详见[site/classes.md](./site/classes.md)和[在线演示](https://js8.in/nodeppt/classes.html)\n\n### 布局\n\nnodeppt 这次使用`webslides`的布局，支持丰富的布局，实在太多了，直接看文档[site/layout.md](./site/layout.md)和[在线演示](https://js8.in/nodeppt/layout.html)\n\n### attribute\n\n参考[markdown-it-attrs](https://www.npmjs.com/package/markdown-it-attrs)，支持了`attribute`，修改增加多 class 支持等功能。\n\n其中：`..class`会往上一级节点添加 class，支持`{.class1.class2}`这种多 class 的语法。用法举例：\n\n```markdown\n# header {.style-me.class2}\n\nparagraph {data-toggle=modal}\n```\n\nOutput:\n\n```html\n<h1 class=\"style-me class2\">header</h1>\n<p data-toggle=\"modal\">paragraph</p>\n```\n\n```markdown\nUse the css-module green on this paragraph. {.text-intro}\n```\n\nOutput:\n\n```html\n<p class=\"text-intro\">Use the css-module green on this paragraph.</p>\n```\n\n```markdown\n-   list item **bold** {.red}\n```\n\nOutput:\n\n```html\n<ul>\n    <li class=\"red\">list item <strong>bold</strong></li>\n</ul>\n```\n\n```markdown\n-   list item **bold**\n    {.red}\n```\n\nOutput:\n\n```html\n<ul class=\"red\">\n    <li>list item <strong>bold</strong></li>\n</ul>\n```\n\n### image 增强\n\n对于 image ，支持外面包裹一层的写法，具体语法 `!![](图片地址 属性)`，例如：\n\n```markdown\n!![](https://webslides.tv/static/images/iphone.png .size-50.alignleft)\n```\n\nOutput：\n\n```html\n<img src=\"https://webslides.tv/static/images/iphone.png\" class=\"size-50 alignleft\" />\n```\n\n```markdown\n!![figure](https://webslides.tv/static/images/setup.png .aligncenter)\n```\n\nOutput:\n\n```html\n<figure><img src=\"https://webslides.tv/static/images/setup.png\" class=\"aligncenter\" /></figure>\n```\n\n### button\n\nnodeppt 的 button 是类似`link`语法的，支持蓝色、圆角、空心和 icon 版本的 button：\n\n```markdown\n[普通按钮](){.button} [圆角普通按钮](){.button.radius}\n\n[空心](){.button.ghost} [:fa-github: 前面带 icon](){.button}\n```\n\n### Icon：FontAwesome\n\nnodeppt 的 icon 支持 [FontAwesome](https://fontawesome.com/)\n语法：\n\n-   `:fa-xxx:` → `<i class=\"fa fa-xxx\"></i>`\n-   `:~fa-xxx:~` → `<span><i class=\"fa fa-xxx\"></i></span>`\n-   `::fa-xxx::` → 块级`<i class=\"fa fa-xxx\"></i>`，即不会被`p`包裹\n\n### span\n\n代码修改自[markdown-it-span](https://github.com/pnewell/markdown-it-span/)，支持 `attr`语法，基本用法：\n\n```md\n:span:\n:span: {.text-span}\n```\n\n### 动效\n\nnodeppt 一如既往的支持动效，2.0 版本支持动效主要是页面内的动效。\n\n支持动效包括：\n\n-   fadeIn\n-   zoomIn\n-   rollIn\n-   moveIn\n-   fadeInUp\n-   slow\n\n在需要支持的动效父节点添加`.build`或者在具体的某个元素上添加`.tobuild+动效 class`即可。\n\n按照惯例，nodeppt 还支持`animate.css`的动效哦~\n\n详细查看文件：[site/animation.md](./site/animation.md)和[在线演示](https://js8.in/nodeppt/animation.html)\n\n### 使用强大的`:::`完成复杂布局\n\n`:::`语法是扩展了 [markdown-it-container](https://www.npmjs.com/package/markdown-it-container) 语法，默认是任意 tag，例如\n\n```markdown\n:::div {.content-left}\n\n## title\n\n:::\n```\n\nOutput：\n\n```html\n<div class=\"content-left\"><h2>title</h2></div>\n```\n\n还支持，`tag` 嵌套，除此之外，支持的组件包括：\n\n-   card：卡片，一边是图片，一边是内容\n-   column：column 多栏布局\n-   shadowbox：带阴影的盒子\n-   steps：步骤组件\n-   cta：\n-   gallery：图片\n-   flexblock：flex block 布局，支持多个子类型\n-   note: 演讲注释\n\n基本语法是：\n\n```markdown\n:::TYPE {.attrs}\n\n## 第一部分\n\n使用 hr 标签隔开\n\n---\n\n## 第二部分\n\n这里的内容也是哦\n\n:::\n```\n\n详细可以看 [component](./site/component.md) 部分的 markdown 文件和[在线演示](https://js8.in/nodeppt/component.html)\n\n<h2 align=\"center\">打印？导出 pdf？</h2>\n\nchrome 浏览器，直接在第一页 `command+P/ctrl+P` 即可\n\n<h2 align=\"center\">高级玩法</h2>\n\n如果上面\n\n### `nodeppt.config.js`\n\n在 nodeppt 执行路径下创建`nodeppt.config.js`文件，可以配置跟`webpack`相关的选项，另外可以支持自研 nodeppt 插件。\n\n默认内置的`config.js`内容如下：\n\n```js\n/**\n * @file 默认配置\n */\nmodule.exports = () => ({\n    // project deployment base\n    baseUrl: '/',\n\n    // where to output built files\n    outputDir: 'dist',\n\n    // where to put static assets (js/css/img/font/...)\n    assetsDir: '',\n\n    // filename for index.html (relative to outputDir)\n    indexPath: 'index.html',\n    // 插件，包括 markdown 和 posthtml\n    plugins: [],\n    // chainWebpack: [],\n\n    // whether filename will contain hash part\n    filenameHashing: true,\n\n    // boolean, use full build?\n    runtimeCompiler: false,\n\n    // deps to transpile\n    transpileDependencies: [\n        /* string or regex */\n    ],\n\n    // sourceMap for production build?\n    productionSourceMap: true,\n\n    // use thread-loader for babel & TS in production build\n    // enabled by default if the machine has more than 1 cores\n    parallel: () => {\n        try {\n            return require('os').cpus().length > 1;\n        } catch (e) {\n            return false;\n        }\n    },\n\n    // multi-page config\n    pages: undefined,\n\n    // <script type=\"module\" crossorigin=\"use-credentials\">\n    // #1656, #1867, #2025\n    crossorigin: undefined,\n\n    // subresource integrity\n    integrity: false,\n\n    css: {\n        extract: true\n        // modules: false,\n        // localIdentName: '[name]_[local]_[hash:base64:5]',\n        // sourceMap: false,\n        // loaderOptions: {}\n    },\n\n    devServer: {\n        /*\n      host: '0.0.0.0',\n      port: 8080,\n      https: false,\n      proxy: null, // string | Object\n      before: app => {}\n    */\n    }\n});\n```\n\n### parser plugin\n\n解析插件分两类： `markdown-it` 和 `posthtml`，\n\n-   markdown-it：是解析 markdown 文件的，如果是增强 markdown 语法，可以用这类插件\n-   posthtml：是处理 html 标签的，如果是修改输出的 html 内容，可以用这类插件\n\n定义一个 plugin ：\n\n```js\nmodule.exports = {\n    // 这里的 id 必须以 markdown/posthtml开头\n    // 分别对应 markdown-it和 posthtml 插件语法\n    id: 'markdown-xxx',\n    // 这里的 apply 是插件实际的内容，详细查看 markdown-it和 posthtml 插件开发\n    apply: () => {}\n};\n```\n\n-   [markdown-it docs](https://github.com/markdown-it/markdown-it/tree/master/docs)\n-   [posthtml docs](https://github.com/posthtml/posthtml/tree/master/docs)\n\n### webslides plugin\n\nWebSlides 插件需要写到一个 js 文件中，然后作为数组放到`window.WSPlugins_`中，然后通过在 md 页面的配置（yaml）添加 js 的方法引入。\n\n```md\njs: - webslide_plugins.js\n```\n\n```js\n// webslide_plugins.js内容\nwindow.WSPlugins_ = [\n    {\n        id: 'webslide_plugin_name',\n        // 下面是对应的插件类\n        apply: class Plugin {}\n    }\n];\n```\n\n参考[WebSlides 文档](https://github.com/webslides/WebSlides/wiki/Plugin-development)\n\n### Template：自制模板\n\n参考[nodeppt-template-default](https://github.com/ksky521/nodeppt-template-default)。\n\n然后使用`nodeppt new username/repo xxx.md`使用\n\n<h2 align=\"center\">Thanks</h2>\n\n-   [WebSlides](https://github.com/webslides/WebSlides)\n-   [markdown-it](https://github.com/markdown-it/markdown-it)\n-   [posthtml](https://github.com/posthtml/posthtml)\n-   [webpack](https://github.com/webpack/webpack)\n-   [vue-cli](https://github.com/vuejs/vue-cli)\n"
  },
  {
    "path": "gh-page.sh",
    "content": "#! /bin/sh\nset -e\nrm -rf publish\ncd site\n\nnode ../packages/nodeppt/bin/nodeppt build index.md\nnode ../packages/nodeppt/bin/nodeppt build animation.md\nnode ../packages/nodeppt/bin/nodeppt build component.md\nnode ../packages/nodeppt/bin/nodeppt build layout.md\nnode ../packages/nodeppt/bin/nodeppt build media.md\nnode ../packages/nodeppt/bin/nodeppt build background.md\nnode ../packages/nodeppt/bin/nodeppt build classes.md\n\ncd dist\n\necho 'nodeppt.js.org' > CNAME\ngit init\ngit add -A\ndate_str=`date \"+DATE: %m/%d/%Y%nTIME: %H:%M:%S\"`\ngit commit -m \"build with nodeppt on $date_str\"\n#exit\necho 'push remote github'\ngit push -u git@github.com:ksky521/nodeppt.git master:gh-pages --force\n"
  },
  {
    "path": "lerna.json",
    "content": "{\n    \"packages\": [\n        \"packages/*\"\n    ],\n    \"npmClient\": \"yarn\",\n    \"command\": {\n        \"bootstrap\": {\n            \"npmClientArgs\": [\n                \"--no-lockfile\"\n            ]\n        }\n    },\n    \"version\": \"2.2.2\"\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n    \"name\": \"nodeppt\",\n    \"private\": true,\n    \"workspaces\": [\n        \"packages/*\"\n    ],\n    \"devDependencies\": {\n        \"lerna\": \"^3.20.2\"\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt/.npmignore",
    "content": "__tests__\n__mocks__\npackage-lock.json\nyarn.lock\n"
  },
  {
    "path": "packages/nodeppt/README.md",
    "content": "# nodeppt 2.0\n\n> 累死累活干不过做 PPT 的！<br/> > **查看效果：https://nodeppt.js.org**\n\n[![Markpress npm badge](https://nodei.co/npm/nodeppt.png)](https://www.npmjs.com/package/nodeppt)\n\n**nodeppt 2.0** 基于[webslides](https://github.com/webslides/WebSlides)、webpack、markdown-it、posthtml 重构，[新效果](https://nodeppt.js.org)\n\n## Install\n\n```bash\nnpm install -g nodeppt\n```\n\n## Usage\n\n简化了，就三个命令：\n\n-   new：使用线上模板创建一个新的 md 文件\n-   serve：启动一个 md 文件的 webpack dev server\n-   build：编译产出一个 md 文件\n\n```bash\n# create a new slide with an official template\n$ nodeppt new slide.md\n\n# create a new slide straight from a github template\n$ nodeppt new slide.md -t username/repo\n\n# start local sever show slide\n$ nodeppt serve slide.md\n\n# to build a slide\n$ nodeppt build slide.md\n```\n\n### 帮助\n\n```bash\n# help\nnodeppt -h\n# 获取帮助\nnodeppt serve -h\n```\n\n## 演讲者模式\n\nnodeppt 有演讲者模式，在页面 url 后面增加`?mode=speaker` 既可以打开演讲者模式，双屏同步\n\n## Keyboard Shortcuts\n\n-   Page: ↑/↓/←/→ Space Home End\n-   Fullscreen: F\n-   Overview: -/+\n-   Speaker Note: N\n-   Grid Background: Enter\n\n## 公共资源：public 文件夹\n\n如果项目文件夹下，存在`public`文件夹，可以直接通过 url 访问，参考`webpack dev server`的 `contentBase` 选项。\n\n在`build`的时候，public 文件夹中的文件会完全 copy 到`dist`文件夹中\n\n## 编写\n\n最佳体验是 chrome 浏览器，本来就是给做演示用的，所以就别考虑非 Chrome 浏览器兼容问题了！\n\n这里说下怎么编写。\n\n### 基本语法\n\n整个 markdown 文件分为两部分，第一部分是写在最前面的**配置**，然后是使用`<slide>`隔开的每页幻灯片内容。\n\n### 配置\n\nnodeppt 的配置是直接写在 md 文件顶部的，采用 yaml 语法，例如下面配置：\n\n```yaml\ntitle: nodeppt markdown 演示\nspeaker: 三水清\nurl: https://github.com/ksky521/nodeppt\njs:\n    - https://www.echartsjs.com/asset/theme/shine.js\nprismTheme: solarizedlight\nplugins:\n    - echarts\n    - katex\n```\n\n-   title: 演讲主题\n-   speaker：演讲者\n-   url：地址\n-   js：js 文件数组，放到 body 之前\n-   css：css 文件数组，放到头部\n-   prismTheme：prism 配色，取值范围 `['dark', 'coy', 'funky', 'okaidia', 'tomorrow', 'solarizedlight', 'twilight']`\n-   plugins：目前支持 [echarts](https://echarts.baidu.com/) 和 [katex](https://katex.org) 两个插件\n-   pluginsOptions：插件的配置\n-   webslidesOptions：[webslides](https://github.com/webslides/WebSlides/wiki/Core-API#options)配置\n\n**webslidesOptions 对应的是 webslides 的配置，例如开启`autoslide`**：\n\n```yaml\nwebslidesOptions:\n    autoslide: 5000\n```\n\n#### 插件\n\n目前 nodeppt 支持 [图表 echarts](https://echarts.baidu.com/) ，[流程图 mermaid](https://mermaidjs.github.io/)，[数学符号 KaTeX](https://katex.org) 3 个插件。\n\n#### echarts\n\necharts 主题配色可以直接在`yaml`配置的 js 中引入。echarts 采用`fence`语法，如下：\n\n```echarts\n{\n    xAxis: {\n        type: 'category',\n        data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']\n    },\n    yAxis: {\n        type: 'value'\n    },\n    series: [{\n        data: [820, 932, 901, 934, 1290, 1330, 1320],\n        type: 'line'\n    }]\n}\n```\n\n详见[site/echarts.md](./site/echarts.md)\n\n#### mermaid\n\nmermaid 主题配色可以直接在`yaml`配置的 js 中引入。mermaid 采用`fence`语法，如下：\n\n```mermaid\nsequenceDiagram\n    Alice ->> Bob: Hello Bob, how are you?\n    Bob-->>John: How about you John?\n    Bob--x Alice: I am good thanks!\n    Bob-x John: I am good thanks!\n    Note right of John: Bob thinks a long<br/>long time, so long<br/>that the text does<br/>not fit on a row.\n\n    Bob-->Alice: Checking with John...\n    Alice->John: Yes... John, how are you?\n```\n\n详见[site/mermaid.md](./site/mermaid.md)\n\n#### ketex\n\n参考：[markdown-it-katex](https://www.npmjs.com/package/markdown-it-katex)语法\n\n### `<slide>` 语法\n\nnodeppt 会根据`<slide>`对整个 markdown 文件进行拆分，拆成单页的幻灯片内容。`<slide>` 标签支持下面标签：\n\n-   class/style 等：正常的 class 类，可以通过这个控制居中（aligncenter），内容位置，背景色等\n-   image：背景图片，基本语法 `image=\"img_url\"`\n-   video：背景视频，基本语法 `video=\"video_src1,video_src2\"`\n-   :class：wrap 的 class，下面详解\n\n每个 slide 会解析成下面的 html 结构：\n\n```html\n<section class=\"slide\" attrs...><div class=\"wrap\" wrap=\"true\">// 具体 markdown 渲染的内容</div></section>\n```\n\n其中`<slide>` 的`class`等会被解析到 `<section>`标签上面，而`:class`则被解析到`div.wrap`上面，例如：\n\n```html\n<slide :class=\"size-50\" class=\"bg-primary\"></slide>\n```\n\noutput 为：\n\n```html\n<section class=\"slide bg-primary\"><div class=\"wrap size-50\" wrap=\"true\">// 具体 markdown 渲染的内容</div></section>\n```\n\n#### 背景：图片\n\n`<slide>`的`image` 会被解析成背景大图，常见的支持方式有：\n\n```md\n<slide image=\"https://source.unsplash.com/UJbHNoVPZW0/\">\n\n# 这是一个普通的背景图\n\n<slide image=\"https://source.unsplash.com/UJbHNoVPZW0/ .dark\">\n\n# 这张背景图会在图片上面蒙一层偏黑色的透明层\n\n<slide image=\"https://source.unsplash.com/UJbHNoVPZW0/ .light\">\n\n# 这张背景图会在图片上面蒙一层偏白色的透明层\n\n<slide class=\"bg-black aligncenter\" image=\"https://source.unsplash.com/n9WPPWiPPJw/ .anim\">\n\n# 这张背景图会缓慢动\n```\n\n详见[site/background.md](./site/background.md)和[在线演示](https://js8.in/nodeppt/background.html)\n\n### 样式\n\n样式太多，具体详见[site/classes.md](./site/classes.md)和[在线演示](https://js8.in/nodeppt/classes.html)\n\n### 布局\n\nnodeppt 这次使用`webslides`的布局，支持丰富的布局，实在太多了，直接看文档[site/layout.md](./site/layout.md)和[在线演示](https://js8.in/nodeppt/layout.html)\n\n### attribute\n\n参考[markdown-it-attrs](https://www.npmjs.com/package/markdown-it-attrs)，支持了`attribute`，修改增加多 class 支持等功能。\n\n其中：`..class`会往上一级节点添加 class，支持`{.class1.class2}`这种多 class 的语法。用法举例：\n\n```markdown\n# header {.style-me.class2}\n\nparagraph {data-toggle=modal}\n```\n\nOutput:\n\n```html\n<h1 class=\"style-me class2\">header</h1>\n<p data-toggle=\"modal\">paragraph</p>\n```\n\n```markdown\nUse the css-module green on this paragraph. {.text-intro}\n```\n\nOutput:\n\n```html\n<p class=\"text-intro\">Use the css-module green on this paragraph.</p>\n```\n\n```markdown\n-   list item **bold** {.red}\n```\n\nOutput:\n\n```html\n<ul>\n    <li class=\"red\">list item <strong>bold</strong></li>\n</ul>\n```\n\n```markdown\n-   list item **bold**\n    {.red}\n```\n\nOutput:\n\n```html\n<ul class=\"red\">\n    <li>list item <strong>bold</strong></li>\n</ul>\n```\n\n### image 增强\n\n对于 image ，支持外面包裹一层的写法，具体语法 `!![](图片地址 属性)`，例如：\n\n```markdown\n!![](https://webslides.tv/static/images/iphone.png .size-50.alignleft)\n```\n\nOutput：\n\n```html\n<img src=\"https://webslides.tv/static/images/iphone.png\" class=\"size-50 alignleft\" />\n```\n\n```markdown\n!![figure](https://webslides.tv/static/images/setup.png .aligncenter)\n```\n\nOutput:\n\n```html\n<figure><img src=\"https://webslides.tv/static/images/setup.png\" class=\"aligncenter\" /></figure>\n```\n\n### button\n\nnodeppt 的 button 是类似`link`语法的，支持蓝色、圆角、空心和 icon 版本的 button：\n\n```markdown\n[普通按钮](){.button} [圆角普通按钮](){.button.radius}\n\n[空心](){.button.ghost} [:fa-github: 前面带 icon](){.button}\n```\n\n### Icon：FontAwesome\n\nnodeppt 的 icon 支持 [FontAwesome](https://fontawesome.com/)\n语法：\n\n-   `:fa-xxx:` → `<i class=\"fa fa-xxx\"></i>`\n-   `:~fa-xxx:~` → `<span><i class=\"fa fa-xxx\"></i></span>`\n-   `::fa-xxx::` → 块级`<i class=\"fa fa-xxx\"></i>`，即不会被`p`包裹\n\n### span\n\n代码修改自[markdown-it-span](https://github.com/pnewell/markdown-it-span/)，支持 `attr`语法，基本用法：\n\n```md\n:span:\n:span: {.text-span}\n```\n\n### 动效\n\nnodeppt 一如既往的支持动效，2.0 版本支持动效主要是页面内的动效。\n\n支持动效包括：\n\n-   fadeIn\n-   zoomIn\n-   rollIn\n-   moveIn\n-   fadeInUp\n-   slow\n\n在需要支持的动效父节点添加`.build`或者在具体的某个元素上添加`.tobuild+动效 class`即可。\n\n按照惯例，nodeppt 还支持`animate.css`的动效哦~\n\n详细查看文件：[site/animation.md](./site/animation.md)和[在线演示](https://js8.in/nodeppt/animation.html)\n\n### 使用强大的`:::`完成复杂布局\n\n`:::`语法是扩展了 [markdown-it-container](https://www.npmjs.com/package/markdown-it-container) 语法，默认是任意 tag，例如\n\n```markdown\n:::div {.content-left}\n\n## title\n\n:::\n```\n\nOutput：\n\n```html\n<div class=\"content-left\"><h2>title</h2></div>\n```\n\n还支持，`tag` 嵌套，除此之外，支持的组件包括：\n\n-   card：卡片，一边是图片，一边是内容\n-   column：column 多栏布局\n-   shadowbox：带阴影的盒子\n-   steps：步骤组件\n-   cta：\n-   gallery：图片\n-   flexblock：flex block 布局，支持多个子类型\n\n**基本语法**是：\n\n```markdown\n:::TYPE {.attrs}\n\n## 第一部分\n\n使用 hr 标签隔开\n\n---\n\n## 第二部分\n\n这里的内容也是哦\n\n:::\n```\n\n详细可以看 [component](./site/component.md) 部分的 markdown 文件和[在线演示](https://js8.in/nodeppt/component.html)\n\n## 打印？导出 pdf？\n\n在 chrome 浏览器，在 URL 添加`URL?print-pdf`，然后直接 `command+P/ctrl+P` 选择打印即可！\n\n## 高级玩法\n\n如果上面\n\n### `nodeppt.config.js`\n\n在 nodeppt 执行路径下创建`nodeppt.config.js`文件，可以配置跟`webpack`相关的选项，另外可以支持自研 nodeppt 插件。\n\n默认内置的`config.js`内容如下：\n\n```js\n/**\n * @file 默认配置\n */\nmodule.exports = () => ({\n    // project deployment base\n    baseUrl: '/',\n\n    // where to output built files\n    outputDir: 'dist',\n\n    // where to put static assets (js/css/img/font/...)\n    assetsDir: '',\n\n    // filename for index.html (relative to outputDir)\n    indexPath: 'index.html',\n    // 插件，包括 markdown 和 posthtml\n    plugins: [],\n    // chainWebpack: [],\n\n    // whether filename will contain hash part\n    filenameHashing: true,\n\n    // boolean, use full build?\n    runtimeCompiler: false,\n\n    // deps to transpile\n    transpileDependencies: [\n        /* string or regex */\n    ],\n\n    // sourceMap for production build?\n    productionSourceMap: true,\n\n    // use thread-loader for babel & TS in production build\n    // enabled by default if the machine has more than 1 cores\n    parallel: () => {\n        try {\n            return require('os').cpus().length > 1;\n        } catch (e) {\n            return false;\n        }\n    },\n\n    // multi-page config\n    pages: undefined,\n\n    // <script type=\"module\" crossorigin=\"use-credentials\">\n    // #1656, #1867, #2025\n    crossorigin: undefined,\n\n    // subresource integrity\n    integrity: false,\n\n    css: {\n        extract: true,\n        // modules: false,\n        // localIdentName: '[name]_[local]_[hash:base64:5]',\n        // sourceMap: false,\n        // loaderOptions: {}\n    },\n\n    devServer: {\n        /*\n      host: '0.0.0.0',\n      port: 8080,\n      https: false,\n      proxy: null, // string | Object\n      before: app => {}\n    */\n    },\n});\n```\n\n### parser plugin\n\n解析插件分两类： `markdown-it` 和 `posthtml`，\n\n-   markdown-it：是解析 markdown 文件的，如果是增强 markdown 语法，可以用这类插件\n-   posthtml：是处理 html 标签的，如果是修改输出的 html 内容，可以用这类插件\n\n定义一个 plugin ：\n\n```js\nmodule.exports = {\n    // 这里的 id 必须以 markdown/posthtml开头\n    // 分别对应 markdown-it和 posthtml 插件语法\n    id: 'markdown-xxx',\n    // 这里的 apply 是插件实际的内容，详细查看 markdown-it和 posthtml 插件开发\n    apply: () => {},\n};\n```\n\n-   [markdown-it docs](https://github.com/markdown-it/markdown-it/tree/master/docs)\n-   [posthtml docs](https://github.com/posthtml/posthtml/tree/master/docs)\n\n### webslides plugin\n\nWebSlides 插件需要写到一个 js 文件中，然后作为数组放到`window.WSPlugins_`中，然后通过在 md 页面的配置（yaml）添加 js 的方法引入。\n\n```md\njs: - webslide_plugins.js\n```\n\n```js\n// webslide_plugins.js内容\nwindow.WSPlugins_ = [\n    {\n        id: 'webslide_plugin_name',\n        // 下面是对应的插件类\n        apply: class Plugin {},\n    },\n];\n```\n\n参考[WebSlides 文档](https://github.com/webslides/WebSlides/wiki/Plugin-development)\n\n### Template：自制模板\n\n参考[nodeppt-template-default](https://github.com/ksky521/nodeppt-template-default)。\n\n然后使用`nodeppt new username/repo xxx.md`使用\n\n## Thanks\n\n-   [WebSlides](https://github.com/webslides/WebSlides)\n-   [markdown-it](https://github.com/markdown-it/markdown-it)\n-   [posthtml](https://github.com/posthtml/posthtml)\n-   [webpack](https://github.com/webpack/webpack)\n-   [vue-cli](https://github.com/vuejs/vue-cli)\n"
  },
  {
    "path": "packages/nodeppt/__tests__/demo.md",
    "content": "title: xxx\n\n[slide]\n# haha\n## hahah121212\n"
  },
  {
    "path": "packages/nodeppt/bin/nodeppt",
    "content": "#!/usr/bin/env node\nconst program = require('commander');\nconst chalk = require('chalk');\nconst semver = require('semver');\n\nconst packageJson = require('../package.json');\n\nconst requiredVersion = packageJson.engines.node;\nfunction checkNodeVersion(wanted, id) {\n    if (!semver.satisfies(process.version, wanted)) {\n        console.log(chalk.red('  You must upgrade node to >=' + wanted + ' to use nodeppt'));\n        process.exit(1);\n    }\n}\ncheckNodeVersion(requiredVersion, packageJson.name);\n\nif (process.argv[2] && process.argv[2] === '-v') {\n    process.argv[2] = '-V';\n}\nprogram.version(packageJson.version);\n\nprogram\n    .command('serve [entry]')\n    .description('start local sever show slide')\n    .option('-p, --port [port]', 'set server port ', 8080)\n    .option('--https', 'use https ', 8080)\n    .option('-d, --dest <dir>', 'output directory')\n    .option('-H, --host [host]', 'set host address', '0.0.0.0')\n\n    .action((entry, cmd) => {\n        require('nodeppt-serve').serve(entry, cleanArgs(cmd));\n    })\n    .on('--help', () => {\n        console.log('  Examples:');\n        console.log();\n        console.log('    nodeppt serve slide.md');\n        console.log('    nodeppt serve slide.md -p 8080');\n        console.log();\n    });\n\nprogram\n    .command('build [entry]')\n    .option('-m, --map', 'Release sourcemap')\n    .option('-d, --dest <dir>', 'output directory')\n    .description('build html file')\n    .action((entry, cmd) => {\n        require('nodeppt-serve').build(entry, cleanArgs(cmd));\n    })\n    .on('--help', () => {\n        console.log('  Examples:');\n        console.log();\n        console.log('    nodeppt build slide.md');\n        console.log();\n    });\n\nprogram\n    .command('new [dest]')\n    .description('Create a new markdown slide')\n    .option('-t, --template [template]', 'template repo or path')\n    .option('-f, --force', 'force', 'Force to delete file exist, default：true')\n    .action((dest, cmd) => {\n        return require('../commands/new')(dest, cleanArgs(cmd));\n    })\n    .on('--help', () => {\n        console.log('  Examples:');\n        console.log();\n        console.log(chalk.gray('    # create a new slide with an official template'));\n        console.log('    $ nodeppt new slide.md');\n        console.log();\n        console.log(chalk.gray('    # create a new slide straight from a github template'));\n        console.log('    $ nodeppt new slide.md -t username/repo');\n    });\n\nprogram.on('--help', () => {\n    console.log('  Examples:');\n    console.log();\n    console.log(chalk.gray('    # create a new slide with an official template'));\n    console.log('    $ nodeppt new slide.md');\n    console.log();\n    console.log(chalk.gray('    # create a new slide straight from a github template'));\n    console.log('    $ nodeppt new slide.md -t username/repo');\n    console.log();\n    console.log(chalk.gray('    # start local sever show slide'));\n    console.log('    $ nodeppt serve slide.md');\n    console.log();\n    console.log(chalk.gray('    # to build a slide'));\n    console.log('    $ nodeppt build slide.md');\n    console.log();\n});\n\nprogram.parse(process.argv);\nif (!program.args[0] && process.argv[2] !== 'new') {\n    process.stdout.write(program.helpInformation());\n    program.emit('--help');\n} else if (!['serve', 'build', 'new'].includes(process.argv[2])) {\n    program.emit('--help');\n}\n\nfunction cleanArgs(cmd) {\n    const args = {version: packageJson.version};\n    cmd.options.forEach((o) => {\n        const key = o.long.replace(/^--/, '');\n        // if an option is not present and Command has a method with the same name\n        // it should not be copied\n        if (typeof cmd[key] !== 'function' && typeof cmd[key] !== 'undefined') {\n            args[key] = cmd[key];\n        }\n    });\n    return args;\n}\n"
  },
  {
    "path": "packages/nodeppt/commands/new.js",
    "content": "/**\n * @file init 初始化项目\n */\nconst path = require('path');\nconst fs = require('fs-extra');\n\nconst home = require('user-home');\nconst inquirer = require('inquirer');\n\nconst exists = fs.existsSync;\nconst rm = fs.removeSync;\n\nconst generate = require('../lib/generate');\n\nconst {\n    chalk,\n    isLocalPath,\n    getTemplatePath,\n    error,\n    updateSpinner,\n    logWithSpinner,\n    stopSpinner,\n    log,\n    downloadRepo,\n    clearConsole\n} = require('nodeppt-shared-utils');\nconst ALIAS_MAP = process.env.alias || {\n    buildin: path.parse(require.resolve('nodeppt-template-default')).dir,\n    default: 'ksky521/nodeppt-template-default'\n};\nconst alias = name => {\n    if (ALIAS_MAP[name]) {\n        return ALIAS_MAP[name];\n    }\n\n    return name;\n};\nmodule.exports = async (dest, opts) => {\n    let template = opts.template;\n    // 使用内置的\n    template = alias(template || 'buildin');\n    if (!dest) {\n        const {filename} = await inquirer.prompt([\n            {\n                name: 'filename',\n                type: 'string',\n                required: true,\n                label: 'Please input markdown file name',\n                default: 'nodeppt.md'\n            }\n        ]);\n        if (!filename) {\n            return;\n        } else {\n            dest = filename;\n        }\n    }\n    if (path.parse(dest).ext === '') {\n        dest += '.md';\n    }\n    if (exists(dest)) {\n        if (opts.force) {\n            // 强力删除\n            await fs.remove(dest);\n        } else {\n            clearConsole();\n            // 提示让删除\n            const {action} = await inquirer.prompt([\n                {\n                    name: 'action',\n                    type: 'list',\n                    message: `${dest} file exists. Continue?：`,\n                    choices: [\n                        {name: 'overwrite', value: 'overwrite'},\n                        {name: 'cancel', value: false}\n                    ]\n                }\n            ]);\n            if (!action) {\n                return;\n            } else if (action === 'overwrite') {\n                log(`delete ${chalk.cyan(dest)}...`);\n                await fs.remove(dest);\n            }\n        }\n    }\n\n\n    if (isLocalPath(template)) {\n        // 使用离线地址\n        // 直接复制，不下载github代码\n        const templatePath = getTemplatePath(template);\n        if (exists(templatePath)) {\n            generate(templatePath, dest, opts);\n        } else {\n            error('Template not found');\n        }\n    } else {\n        // 临时存放地址，存放在~/.nodeppt-templates 下面\n        let tmp = path.join(home, '.nodeppt-templates', template.replace(/[/:#]/g, '-'));\n\n        clearConsole();\n        logWithSpinner('🗃', 'Download...');\n        if (exists(tmp)) {\n            rm(tmp);\n        }\n\n        downloadRepo(template, tmp, opts, err => {\n            if (!err) {\n                updateSpinner('🗃', 'Template download success！');\n            } else {\n                updateSpinner('❌', 'Template download error!');\n            }\n            stopSpinner();\n            console.log();\n            if (!err) {\n                generate(tmp, dest, opts);\n            } else {\n                error('Failed to download repo ' + template + ': ' + err.message.trim());\n                if (!process.env.DEBUG) {\n                    log(`Use「${chalk.bgYellow.black('DEBUG=*')}」 to get more info`);\n                }\n            }\n        });\n    }\n};\n"
  },
  {
    "path": "packages/nodeppt/lib/ask.js",
    "content": "/**\n * @file 修改字 vue-cli,prompt 收集答案\n */\n\nconst inquirer = require('inquirer');\nconst {evaluate} = require('nodeppt-shared-utils');\n\nconst promptMapping = {\n    string: 'input',\n    boolean: 'confirm'\n};\n\nmodule.exports = (prompts, data) => {\n    const answers = {};\n    let keys = Object.keys(prompts);\n\n    return new Promise(async (resolve, reject) => {\n        let key;\n        while ((key = keys.shift())) {\n            await prompt(answers, key, prompts[key], data);\n        }\n        resolve(answers);\n    });\n};\n// 将 default 使用 templateData 渲染一下，比如作者之类的\nfunction render(content, data) {\n    if (content && /{{([^{}]+)}}/g.test(content)) {\n        Object.keys(data).forEach(key => {\n            if (data[key] && typeof data[key] === 'string') {\n                content = content.split(new RegExp(`{{\\\\s*${key}\\\\s*}}`, 'g')).join(data[key]);\n            }\n        });\n        return content;\n    }\n\n    return content;\n}\nasync function prompt(data, key, prompt, tplData) {\n    // 当 when 起作用的时候跳过\n    if (prompt.when && !evaluate(prompt.when, data)) {\n        return;\n    }\n\n    let promptDefault = prompt.default;\n    if (typeof promptDefault === 'function') {\n        promptDefault = function() {\n            return prompt.default.bind(this)(data);\n        };\n    }\n\n    const answers = await inquirer.prompt([\n        {\n            type: promptMapping[prompt.type] || prompt.type,\n            name: key,\n            message: prompt.message || prompt.label || key,\n            default: render(promptDefault, tplData),\n            choices: prompt.choices || [],\n            validate: prompt.validate || (() => true)\n        }\n    ]);\n\n    if (Array.isArray(answers[key])) {\n        data[key] = {};\n        answers[key].forEach(multiChoiceAnswer => {\n            data[key][multiChoiceAnswer] = true;\n        });\n    } else if (typeof answers[key] === 'string') {\n        data[key] = answers[key].replace(/\"/g, '\\\\\"');\n    } else {\n        data[key] = answers[key];\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt/lib/generate.js",
    "content": "/**\n * @file 根据模板生成项目目录结构\n */\nconst path = require('path');\nconst fs = require('fs');\n\nconst exists = fs.existsSync;\n\nconst semver = require('semver');\nconst Handlebars = require('handlebars');\nconst username = require('username');\nconst render = require('consolidate').handlebars.render;\nconst {\n    error,\n    log,\n    chalk,\n    success,\n    line,\n    getGitUser,\n    newVersionLog,\n    getLatestVersion,\n    logWithSpinner,\n    updateSpinner,\n    stopSpinner,\n    getDebugLogger\n} = require('nodeppt-shared-utils');\nconst {name, version: localVersion} = require('../package.json');\nconst debug = getDebugLogger('generate', name);\n\nconst ask = require('./ask');\n\nlet newVersion = 0;\ngetLatestVersion().then(latest => {\n    if (semver.lt(localVersion, latest)) {\n        newVersion = latest;\n    }\n});\n// 增加 handleba helper\nHandlebars.registerHelper('if_eq', (a, b, opts) => {\n    return a === b ? opts.fn(this) : opts.inverse(this);\n});\n\nHandlebars.registerHelper('unless_eq', (a, b, opts) => {\n    return a === b ? opts.inverse(this) : opts.fn(this);\n});\n\nmodule.exports = async (src, dest, cmdOpts) => {\n    // 0. 设置meta信息\n    const opts = getMetadata(src);\n    try {\n        opts.username = await username();\n    } catch (e) {\n        const {name: gitUser} = getGitUser();\n        opts.username = gitUser;\n    }\n\n    debug(opts);\n\n    // 1. 添加 handlebar helper\n    // eslint-disable-next-line\n    opts.helpers &&\n        Object.keys(opts.helpers).map(key => {\n            Handlebars.registerHelper(key, opts.helpers[key]);\n        });\n\n    // 2. 请回答\n    const answers = await ask(opts.prompts || {}, opts);\n    const data = Object.assign(\n        {\n            filename: dest,\n            inPlace: dest === process.cwd(),\n            noEscape: true\n        },\n        answers\n    );\n    debug(data);\n    console.log();\n    logWithSpinner('🐝', '开始初始化模板...');\n\n    const tpl = getTemplateContent(src);\n    const content = await template(tpl, data);\n\n    fs.writeFileSync(path.resolve(dest), content);\n\n    updateSpinner('🐝', `${dest} create success!`);\n    stopSpinner();\n    line(' 🎉  Success! ');\n\n    if (typeof opts.complete === 'function') {\n        // 跟 vue template 参数保持一致\n        opts.complete(data, {\n            chalk,\n            logger: {\n                log,\n                fatal: error,\n                success: success\n            }\n        });\n\n        if (newVersion) {\n            // 存在新版本\n            newVersionLog(localVersion, newVersion);\n        }\n    } else {\n        logMessage(opts.completeMessage, data);\n    }\n};\n\nfunction logMessage(message, data) {\n    if (isHandlebarTPL(message)) {\n        render(message, data)\n            .then(res => {\n                log(res);\n            })\n            .catch(err => {\n                error('\\n   Error when rendering template complete message：' + err.message.trim());\n                debug(message, data, err);\n            });\n    } else if (message) {\n        log(message);\n    }\n\n    if (newVersion) {\n        newVersionLog(localVersion, newVersion);\n    }\n}\n\nfunction getMetadata(dir) {\n    const json = path.join(dir, 'meta.json');\n    const js = path.join(dir, 'meta.js');\n    let opts = {};\n\n    if (exists(json)) {\n        const content = fs.readFileSync(json, 'utf-8');\n        opts = JSON.parse(content);\n    } else if (exists(js)) {\n        const req = require(path.resolve(js));\n        if (req !== Object(req)) {\n            throw new Error('meta.js syntax error');\n        }\n\n        opts = req;\n    }\n\n    return opts;\n}\n\nfunction getTemplateContent(dir) {\n    const md = path.join(dir, 'template.md');\n    const markdown = path.join(dir, 'template.markdown');\n    if (exists(md)) {\n        const content = fs.readFileSync(md, 'utf-8');\n        return content;\n    } else if (exists(markdown)) {\n        const content = fs.readFileSync(markdown, 'utf-8');\n        return content;\n    } else {\n        throw new Error('template.md not exist');\n    }\n}\n\nfunction isHandlebarTPL(content) {\n    return /{{([^{}]+)}}/g.test(content);\n}\nfunction template(content, data) {\n    if (!isHandlebarTPL(content)) {\n        return Promise.resolve(content);\n    }\n\n    return render(content, data);\n}\n"
  },
  {
    "path": "packages/nodeppt/package.json",
    "content": "{\n    \"name\": \"nodeppt\",\n    \"jsname\": \"nodeppt\",\n    \"description\": \"A simple, in-browser, markdown-driven presentation framework\",\n    \"version\": \"2.2.1\",\n    \"site\": \"https://github.com/ksky521/nodeppt\",\n    \"main\": \"index.js\",\n    \"author\": {\n        \"name\": \"Theo Wang\",\n        \"email\": \"ksky521@gmail.com\"\n    },\n    \"bin\": {\n        \"nodeppt\": \"bin/nodeppt\"\n    },\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"git://github.com/ksky521/nodeppt\"\n    },\n    \"engines\": {\n        \"node\": \">=8.9\"\n    },\n    \"dependencies\": {\n        \"commander\": \"^4.1.0\",\n        \"consolidate\": \"^0.15.1\",\n        \"fs-extra\": \"^8.1.0\",\n        \"handlebars\": \"^4.0.12\",\n        \"inquirer\": \"^7.0.3\",\n        \"nodeppt-js\": \"^2.2.1\",\n        \"nodeppt-parser\": \"^2.2.1\",\n        \"nodeppt-serve\": \"^2.2.1\",\n        \"nodeppt-shared-utils\": \"^2.2.1\",\n        \"nodeppt-template-default\": \"^1.0.2\",\n        \"semver\": \"^7.1.1\",\n        \"user-home\": \"^2.0.0\",\n        \"username\": \"^5.1.0\"\n    },\n    \"keywords\": [\n        \"presentation\",\n        \"powerpoint\",\n        \"slideshow\",\n        \"keynote\",\n        \"ppt\",\n        \"slide\",\n        \"revealjs\",\n        \"impressjs\",\n        \"markdown-it\",\n        \"posthtml\",\n        \"webpack\",\n        \"nodeppt\",\n        \"markdown\"\n    ],\n    \"readmeFilename\": \"README.md\",\n    \"license\": \"MIT\",\n    \"gitHead\": \"9fba34ba1a8cc3ab149c2447c313e25b1e25093e\"\n}\n"
  },
  {
    "path": "packages/nodeppt-js/.npmignore",
    "content": "__tests__\n__mocks__\npackage-lock.json\nyarn.lock\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/_base.less",
    "content": "// sass-lint:disable no-vendor-prefixes\n\n/*=========================================\n1. Base --> Baseline: 8px = .8rem\n=========================================== */\n\n/* -- Disable elastic scrolling/bounce:\nwebslides.js will add .ws-ready automatically. Don't worry :) -- */\n\n.ws-ready {\n    &,\n    body {\n        height: 100%;\n        overflow: hidden;\n        width: 100%;\n    }\n\n    &.ws-ready-zoom {\n        overflow: visible;\n\n        body {\n            overflow: auto;\n        }\n    }\n}\n\n#webslides {\n    -ms-overflow-style: none;\n    -webkit-overflow-scrolling: touch; // sass-lint:disable-line no-misspelled-properties\n    height: 100vh;\n    overflow-x: hidden;\n    overflow-y: scroll;\n\n    &::-webkit-scrollbar {\n        display: none;\n    }\n}\n\nli li {\n    margin-left: 1.6rem;\n}\n\na,\na:active,\na:focus,\na:visited,\ninput:focus,\ntextarea:focus,\nbutton {\n    text-decoration: none;\n    transition: all 0.3s ease-out;\n}\n\np a:active {\n    position: relative;\n    top: 2px;\n}\n\nnav a[rel='external'] em,\n.hidden {\n    clip: rect(1px, 1px, 1px, 1px);\n    height: 1px;\n    overflow: hidden;\n    position: absolute;\n    width: 1px;\n}\n\n.shadow {\n    position: relative;\n\n    &:before,\n    &:after {\n        bottom: 1.6rem;\n        content: '';\n        max-width: 300px;\n        position: absolute;\n        top: 80%;\n        width: 50%;\n        z-index: -1;\n    }\n\n    &:after {\n        right: 2.4rem;\n        transform: rotate(3deg);\n    }\n\n    &:before {\n        left: 2.4rem;\n        transform: rotate(-3deg);\n    }\n}\n\n/*=== 1.1 WRAP/CONTAINER === */\n.wrap,\nheader nav,\nfooter nav {\n    margin-left: auto;\n    margin-right: auto;\n    max-width: 100%;\n    position: relative;\n    width: 100%;\n    z-index: 2;\n\n    @media (min-width: 1024px) {\n        width: 90%;\n    }\n}\n\n.frame,\n.shadow {\n    padding: 2.4rem;\n}\n\n.radius {\n    border-radius: 0.4rem;\n}\n\n.alignright {\n    float: right;\n}\n\n.alignleft {\n    float: left;\n}\n\n.aligncenter {\n    margin-left: auto;\n    margin-right: auto;\n    text-align: center;\n}\n\nimg.aligncenter,\nfigure.aligncenter {\n    display: block;\n    margin-bottom: 0.8rem;\n    margin-top: 0.8rem;\n}\n\nimg.alignleft,\nfigure.alignleft,\nimg.alignright,\nfigure.alignright,\nimg.aligncenter,\nfigure.aligncenter {\n    margin-bottom: 3.2rem;\n    margin-top: 3.2rem;\n}\n\nimg.alignright,\nsvg.alignright,\nfigure.alignright {\n    margin: 0.8rem 0 0.8rem 2.4rem;\n}\n\nimg.alignleft,\nsvg.alignleft,\nfigure.alignleft {\n    margin: 0.8rem 2.4rem 0.8rem 0;\n}\n\n@sizes: 80, 70, 60, 50, 40, 30, 20;\n\n/*=== div.size-60, img.size-50, h1.size-40, p.size-30... === */\n@media (min-width: 1024px) {\n    each(@sizes, {\n        .size-@{value} {\n            width: @value * 1%;\n        }\n    });\n}\n\npre,\ncode {\n    font-family: 'Cousine', monospace;\n}\n\npre {\n    font-size: 1.6rem;\n    line-height: 2.4rem;\n    overflow: auto;\n    padding: 2.4rem;\n    text-align: left;\n    white-space: pre-wrap;\n    width: 100%;\n    word-wrap: break-word;\n\n    & + p {\n        margin-top: 3.2rem;\n    }\n\n    code {\n        padding: 0;\n    }\n}\n\ncode {\n    padding: 0.4rem;\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/_color.less",
    "content": "// sass-lint:disable no-color-literals\n// sass-lint:disable no-vendor-prefixes\n/*=========================================\n19. Colors\n=========================================== */\n\n/* -- Disable elastic scrolling/bounce:\nwebslides.js will add .ws-ready automatically. Don't worry :) -- */\n\nbody {\n    background-color: @body-bg;\n    color: @body-color;\n}\n\n:focus {\n    box-shadow: @focus-box-shadow;\n}\n\nsvg {\n    fill: currentColor;\n}\n\n[class*='bg-'] a,\n[class*='bg-gradient-'] a {\n    color: @spindle;\n}\n\n.bg-brown a {\n    color: @link-color-secondary;\n}\n\na,\n.bg-white a,\n.bg-light a,\n.bg-gradient-white a {\n    color: @link-color;\n}\n\na:hover {\n    color: @link-hover;\n}\n\n.flexblock li > a,\n[class*='bg-'] li > a,\n[class*='bg-gradient-'] li > a,\narticle header a {\n    color: inherit;\n}\n\nhr {\n    background: @hr-bg;\n}\n\nhr:after {\n    background-color: fade(@white, (0.8 * 100));\n    color: @body-color;\n}\n\nabbr,\nacronym {\n    border-bottom: 1px dotted @body-bg;\n}\n\nmark,\nins {\n    background-color: fade(@pattens-blue, (0.8 * 100));\n    color: inherit;\n}\n\n::-moz-selection {\n    background-color: fade(@pattens-blue, (0.8 * 100));\n}\n\n::-webkit-selection {\n    background-color: fade(@pattens-blue, (0.8 * 100));\n}\n\n::selection {\n    background-color: fade(@pattens-blue, (0.8 * 100));\n}\n\npre {\n    background: @white;\n    border: 1px solid fade(@stratos, (0.1 * 100));\n    box-shadow: 0 8px 16px fade(@stratos, (0.04 * 100)), 0 4px 16px fade(@black, (0.08 * 100));\n    &:hover {\n        box-shadow: 0 8px 16px rgba(0, 40, 160, 0.08), 0 8px 24px fade(@black, (0.08 * 100));\n    }\n}\npre.no-style {\n    background: transparent;\n    border: none;\n    box-shadow: none;\n\n    &:hover {\n        box-shadow: none;\n    }\n}\n\ncode,\n[class*='bg-'] pre {\n    background-color: fade(@white, (0.09 * 100));\n}\n\n.bg-white code {\n    background: fade(@stratos, (0.03 * 100));\n}\n\n/*================================================\nSlides - Backgrounds <section class=\"bg-primary\">\n================================================== */\n\n/*3 Corp Colors*/\n\n@bg-color-names: primary, secondary, light, black, black-blue, blue, brown, gray, green, purple, red, white, facebook;\n@bg-colors: @royal-blue, @havelock-blue, @catskill-white, @cod-gray, @big-stone, @rhino, @gray-brown, @mischka,\n    @pine-green, @purple-heart, @cardinal, @white, @facebook;\n\n.bg-colors-mixin(@names; @colors; @index) when (iscolor(extract(@colors, @index))) and (@index > 0) {\n    .bg-colors-mixin(@names; @colors; (@index - 1));\n\n    @name: extract(@names, @index);\n    @color: extract(@colors, @index);\n\n    .bg-@{name} {\n        background-color: @color;\n    }\n}\n// prettier-ignore\n.bg-colors-mixin(@bg-color-names; @bg-colors;length(@bg-colors));\n\n[class*='bg-'] .bg-white {\n    color: @body-color;\n    text-shadow: none;\n}\n\n/* BG Apple Keynote*/\n\n.bg-apple {\n    background: linear-gradient(to bottom, @black 0%, #1a2028 50%, #293845 100%);\n}\n\n/*Font Color*/\n\n.bg-trans-dark,\n.bg-trans-gradient,\n.bg-primary,\n.bg-secondary,\n.bg-blue,\n.bg-green,\n.bg-purple,\n.bg-red,\n.bg-facebook,\n.bg-apple,\n[class*='bg-black'],\n[class*='bg-gradient-'] {\n    color: @white;\n    text-shadow: 0 1px 0 #013;\n}\n\n.bg-light p {\n    color: #456;\n}\n\n.bg-brown p {\n    color: #666;\n}\n\n/*Transparent/Opacity*/\n\n.bg-trans-dark {\n    background: fade(@black, (0.8 * 100));\n}\n\n.bg-trans-light {\n    background: fade(@black, (0.2 * 100));\n}\n\n/*Covers/Longforms...*/\n\n.bg-trans-gradient {\n    background: linear-gradient(to top, fade(@black, (0.8 * 100)) 0%, fade(@black, (0 * 100)) 100%);\n}\n\n/*Horizontal Gradient*/\n\n.bg-gradient-h {\n    background: linear-gradient(134deg, #32b 0, #62b 100%);\n}\n\n/*Vertical Gradient*/\n\n.bg-gradient-v {\n    background: linear-gradient(to top, #62b 0%, #32b 100%);\n}\n\n/*Radial Gradient*/\n\n.bg-gradient-r {\n    background: radial-gradient(ellipse at center, #62b 0%, #32b 100%);\n}\n\n/*White Gradient (vertical)*/\n\n.bg-gradient-white {\n    background: linear-gradient(180deg, #f2f4f6 0, @white 100%);\n    color: @body-color;\n    text-shadow: none;\n}\n\n/*Gray Gradient (horizontal)*/\n\n.bg-gradient-gray {\n    background: linear-gradient(90deg, #f7f9fb 0, #dee2e6 100%);\n    color: @body-color;\n    text-shadow: none;\n}\n\n/*Border/Frame*/\n\n.frame {\n    border: 0.8rem solid @white;\n}\n\n[class*='background'].frame {\n    border-width: 0.2rem;\n}\n\n/*Layer/Box Shadow*/\n\n.shadow,\n.pre {\n    position: relative;\n}\n\n.shadow:before,\n.shadow:after {\n    box-shadow: 0 16px 24px fade(@stratos, (0.3 * 100));\n}\n\n/*============================\nTYPOGRAPHY\n============================== */\n\n/* -- Horizontal separator  -- */\n\n.text-separator:before {\n    background-color: rgba(170, 0, 0, 0.8);\n}\n\n/* -- Pull Quote (Right/Left)  -- */\n\n[class*='text-pull-'] {\n    border-top: 4px solid fade(@black, (0.5 * 100));\n}\n\nimg[class*='text-pull-'],\nfigure[class*='text-pull-'] {\n    border-top: 0;\n}\n\n/* -- Context  -- */\n\n[class*='bg-'] .text-context:before {\n    background-color: @white;\n}\n\n.text-context:before,\n.bg-white .text-context:before {\n    background-color: fade(@stratos, (0.2 * 100));\n}\n\n/* -- Text shadow  -- */\n\n.text-shadow {\n    text-shadow: 0 0 40px fade(@black, (0.5 * 100));\n}\n\n/* -- time, ampersands, prepositions (for, of...), symbols...\n[class*='card-'] time,\nh1 span {\n  color: #abd;\n}\n\n/* -- <pre> comment  -- */\n\n.code-comment {\n    color: rgba(70, 170, 130, 0.9);\n    text-shadow: none;\n}\n\n/*=========================================\nHeader/Nav\n=========================================== */\n\nheader[role='banner'] {\n    background-color: @white;\n}\n\n.logo a {\n    color: inherit;\n}\n\nnav[role='navigation'] li {\n    &.active a {\n        background-color: #555;\n        color: @white;\n    }\n    a {\n        background-color: rgba(50, 50, 50, 0.9);\n        color: @white;\n        &:hover {\n            background-color: rgba(50, 50, 50, 0.7);\n        }\n    }\n}\n\n@socials: twitter, facebook, linkedin, dribbble, github, email;\n@socials-colors: #1da1f3, @facebook, #1683bb, #ea4c89, #60b044, #dd4b39;\n\n.socials-colors-mixin(@names; @colors; @index) when (iscolor(extract(@colors, @index))) and (@index > 0) {\n    .socials-colors-mixin(@names; @colors; (@index - 1));\n\n    @name: extract(@names, @index);\n    @color: extract(@colors, @index);\n\n    nav li.@{name} a:hover {\n        background-color: @color;\n    }\n}\n// prettier-ignore\n.socials-colors-mixin(@socials; @socials-colors;length(@socials-colors));\n\n/*===================================================\n.flexblock li hover/active\n===================================================== */\n\n.flexblock li.active a,\n.metrics li:hover,\n.specs li:hover,\n.reasons li:hover {\n    background-color: fade(@stratos, (0.03 * 100));\n}\n\n/*=========================================\nFeatures & Clients List\n=========================================== */\n\n.features li,\n.clients li {\n    background-color: fade(@white, (0.9 * 100));\n}\n\n[class*='bg-'] .features li,\n[class*='bg-'] .clients li {\n    background-color: fade(@white, (0.1 * 100));\n}\n\n.features li:hover,\n.clients li:hover {\n    box-shadow: 0 8px 16px fade(@stratos, (0.02 * 100)), 0 4px 16px fade(@black, (0.08 * 100));\n}\n\n/*============================\n.flexblock with border\n============================== */\n\n.border {\n    border-bottom: 1px solid fade(@stratos, (0.1 * 100));\n    border-right: 1px solid fade(@stratos, (0.1 * 100));\n}\n\n.border li {\n    border-left: 1px solid fade(@stratos, (0.1 * 100));\n    border-top: 1px solid fade(@stratos, (0.1 * 100));\n}\n\n.flexblock.border li li {\n    border: 0;\n}\n\n/*===========================================\nflexblock.steps\n============================================= */\n\n.steps li:nth-child(1) {\n    background-color: #e8eef7;\n}\n\n.steps li:nth-child(2) {\n    background-color: #dde5f3;\n}\n\n.steps li:nth-child(3) {\n    background-color: #cdd8ec;\n}\n\n.steps li:nth-child(4) {\n    background-color: #bbcdec;\n}\n\n.process {\n    border-bottom: 15px solid transparent;\n    border-top: 15px solid transparent;\n}\n\n.steps li:hover,\n.steps.blink li:hover > a {\n    background-color: #b8cef7;\n}\n\n@media (min-width: 1024px) {\n    .process.step-2 {\n        border-left-color: #e8eef7;\n    }\n    .process.step-3 {\n        border-left-color: #dde5f3;\n    }\n    .process.step-4 {\n        border-left-color: #cdd8ec;\n    }\n    .steps li:hover + li [class*='step-'] {\n        border-left-color: #b8cef7;\n    }\n}\n\n/*=========================================================\nItems: You can use for settings, drag&drop, close/delete...\n=========================================================== */\n\n.specs li:after {\n    background: linear-gradient(\n        to right,\n        fade(@stratos, (0 * 100)) 0%,\n        fade(@stratos, (0.2 * 100)) 50%,\n        fade(@stratos, (0 * 100)) 100%\n    );\n}\n\n.specs li:last-child:after {\n    background: none;\n}\n\n/*=========================================================\nWhy/Steps/Motivation/Reasons -  Decimal/Numbers\n=========================================================== */\n\n.reasons li:after {\n    background: linear-gradient(\n        to right,\n        fade(@stratos, (0 * 100)) 0%,\n        fade(@stratos, (0.2 * 100)) 50%,\n        fade(@stratos, (0 * 100)) 100%\n    );\n}\n\n.reasons li:last-child:after {\n    background: none;\n}\n\n/*=========================================\nOverlays\n=========================================== */\n\n.overlay {\n    background-color: fade(@black, (0.2 * 100));\n}\n\nli:hover .overlay {\n    background-color: fade(@black, (0.1 * 100));\n}\n\n.overlay,\n.overlay a {\n    color: @white;\n    text-shadow: 0 1px 0 #111;\n}\n\n/*=========================================\nGallery li+.overlay+image\n=========================================== */\n\n.gallery li {\n    background-color: fade(@stratos, (0.06 * 100));\n    box-shadow: 0 1px 1px fade(@black, (0.2 * 100)), 0 4px 8px fade(@black, (0.03 * 100));\n}\n\n.gallery li figcaption {\n    background-color: @white;\n}\n\n.flexblock.gallery li:hover {\n    box-shadow: 0 1px 1px fade(@black, (0.2 * 100)), 0 4px 8px fade(@black, (0.08 * 100));\n}\n\n.gallery li footer {\n    border-top: 1px solid fade(@stratos, (0.1 * 100));\n}\n\n.gallery li a {\n    color: @body-color;\n    text-shadow: none;\n}\n\n.flesblock.gallery li a footer {\n    color: #aaa;\n}\n\n/*Arrow */\n\n.gallery li figcaption:before {\n    border: 0.8rem solid @black;\n    border-color: transparent transparent @white @white;\n}\n\n/*=========================================\nPlans / Pricing\n=========================================== */\n\n.plans > li div,\n.flexblock.plans li:hover div {\n    background-color: @white;\n}\n\n.plans > li:hover,\n.plans > li:nth-child(2) {\n    box-shadow: 0 1px 1px fade(@black, (0.1 * 100)), 0 8px 16px fade(@black, (0.1 * 100));\n}\n\n.plans:hover li:nth-child(2):not(:hover) {\n    box-shadow: none;\n}\n\n.plans li h2 {\n    background-color: fade(@stratos, (0.5 * 100));\n    color: @white;\n}\n\n.plans ul li {\n    border-bottom: 1px solid fade(@stratos, (0.1 * 100));\n    &:last-child {\n        border-bottom: 0;\n    }\n}\n\n.plans > li > a {\n    color: @body-color;\n    text-shadow: none;\n}\n\n/*============================\nActivity/CV/Timeline/News\n============================== */\n\n.activity li {\n    border-top: 0.1rem solid fade(@stratos, (0.1 * 100));\n}\n\n.activity li:hover {\n    background-color: fade(@stratos, (0.02 * 100));\n}\n\n/*=========================================\nResume/Work/CV/Portfolio\n=========================================== */\n\n.work-label,\n.work li a {\n    border-bottom: 1px solid fade(@stratos, (0.1 * 100));\n}\n\n.work li:nth-child(odd) > a {\n    background-color: fade(@stratos, (0.03 * 100));\n}\n\n.work li a:hover {\n    background-color: fade(@stratos, (0.04 * 100));\n}\n\n/*===========================================\nClients / Services / Logos...\n============================================= */\n\n.clients.border figcaption {\n    border-top: 1px solid fade(@stratos, (0.1 * 100));\n}\n\n/*====================\nLOGOS\n====================== */\n\n/* --- Images (black logo/image) --- */\n\nimg.blacklogo {\n    background: none;\n    filter: grayscale(100%) brightness(10%) contrast(100%);\n}\n\n/* --- Images (gray logo/image) --- */\n\nimg.graylogo {\n    filter: grayscale(100%) brightness(10%) contrast(10%);\n}\n\n/* --- Images (white Logo/Image) --- */\n\nimg.whitelogo {\n    filter: brightness(0) invert(1);\n}\n\n/* --- Logo/Images Hover --- */\n\nli:hover img.blacklogo,\nli:hover img.graylogo,\nimg.blacklogo:hover,\nimg.graylogo:hover {\n    background: none;\n    filter: grayscale(0%);\n    transition: all 0.6s ease;\n}\n\n/*=========================================================\nCards\n=========================================================== */\n\n[class*='card-'] > a {\n    color: inherit;\n}\n\n/* ---  card ul specs --- */\n\n.description > li {\n    border-bottom: 1px solid fade(@stratos, (0.1 * 100));\n}\n\n.description > li:last-child {\n    border-bottom: 0;\n}\n\n/*== Figure Background === */\n\n[class*='card-'][class*='bg-'] figure {\n    background-color: fade(@stratos, (0.06 * 100));\n}\n\n/*== Ficaption Cards === */\n\n[class*='card'] figcaption,\n[class*='card'] figcaption a {\n    background: linear-gradient(to bottom, fade(@black, (0 * 100)) 0%, fade(@black, (0.2 * 100)) 100%);\n    color: @white;\n}\n\n/*===CTA (Call to Action - Numbers, Price, Promo...)  ===== */\n\n@media (min-width: 768px) {\n    .cta .benefit {\n        border-image: linear-gradient(90deg, transparent, fade(@black, (0.4 * 100)) 50%, transparent) 1 100%;\n        border-left-width: 1px;\n        border-style: solid;\n    }\n}\n\n/*=========================================\nTables\n=========================================== */\n\ntable.table-dark {\n    td,\n    th,\n    thead {\n        border: 1px solid fade(@black, (0.5 * 100));\n    }\n    thead {\n        background-color: fade(@black, (0.3 * 100));\n    }\n    tr:nth-child(even) > td {\n        background: fade(@black, (0.1 * 100));\n    }\n    tr > td {\n        border-top: 1px solid fade(@black, (0.5 * 100));\n    }\n    td:hover,\n    tr:nth-child(even) > td:hover {\n        background-color: fade(@white, (0.5 * 100));\n    }\n}\n\ntable td,\nth,\nthead {\n    border: 1px solid fade(@stratos, (0.1 * 100));\n}\n\ntr:nth-child(even) > td {\n    background-color: fade(@stratos, (0.03 * 100));\n}\n\ntr > td {\n    border-bottom: 1px solid fade(@stratos, (0.1 * 100));\n}\n\ntd:hover,\ntr:nth-child(even) > td:hover {\n    background-color: fade(@stratos, (0.04 * 100));\n}\n\n/*============================\nBrowser (Screenshots)\n============================== */\n\n.browser {\n    border: 1px solid fade(@stratos, (0.1 * 100));\n}\n\n.browser:hover {\n    box-shadow: 0 1px 1px fade(@black, (0.1 * 100)), 0 8px 16px fade(@black, (0.1 * 100));\n}\n\n/*=== Topbar === */\n\n.browser:before {\n    background-color: fade(@stratos, (0.1 * 100));\n    border-bottom: 1px solid fade(@stratos, (0.2 * 100));\n    color: fade(@white, (0.9 * 100));\n}\n\n.browser:hover:before {\n    background-color: fade(@stratos, (0.12 * 100));\n    color: @white;\n}\n\n/*=========================================\nForms\n=========================================== */\n\ninput,\ntextarea {\n    background-color: #fafbfc;\n}\n\ninput:focus,\ntextarea:focus {\n    background-color: @white;\n    box-shadow: 0 0 5px rgba(81, 203, 238, 1);\n}\n\ninput:focus::-moz-placeholder {\n    color: #ddd;\n}\n\ninput:focus::-webkit-input-placeholder {\n    color: #ddd;\n}\n\na.button,\n[class*='badge-'],\nbutton[type='submit'],\ninput {\n    box-shadow: 0 10px 16px -8px fade(@stratos, (0.3 * 100));\n}\n\nbutton,\ninput,\nselect,\ntextarea,\nbutton[type='submit'],\ninput[type='submit'],\n.button,\n.button:hover,\nbutton[type='submit']:hover,\ninput[type='submit']:hover {\n    border: 1px solid @royal-blue;\n}\n\nbutton[type='submit'],\ninput[type='submit'],\n.button,\n.button:hover,\nbutton[type='submit']:hover,\ninput[type='submit']:hover {\n    background-color: @royal-blue;\n    color: @white;\n    text-shadow: 0 1px 0 #123;\n}\n\n.button:active,\nbutton[type='submit']:active,\ninput[type='submit']:active {\n    background-color: #17d;\n}\n\n.ghost,\n.ghost:hover {\n    background: none;\n    color: inherit;\n    text-shadow: none;\n}\n\n.bg-primary select,\n.bg-primary textarea,\n.bg-primary .button,\n.bg-primary button,\n.bg-primary button:hover,\n.bg-primary input,\n[class*='bg-gradient-'] .button,\n[class*='bg-'] a.button.ghost {\n    border-color: @white;\n}\n\n[class*='bg-'] a.button {\n    color: @white;\n}\n\n.bg-white a.button.ghost,\n.bg-gradient-white a.button.ghost {\n    border: 1px solid @royal-blue;\n    color: @body-color;\n}\n\n:disabled,\nbutton:disabled:hover {\n    background-color: #eee;\n    border-color: #eee;\n    color: #ccc;\n}\n\nfieldset {\n    background-color: fade(@stratos, (0.2 * 100));\n    border: 1px solid @royal-blue;\n}\n\nlegend {\n    background-color: fade(@black, (0.6 * 100));\n    color: @white;\n}\n\n/* Inputs/Buttons - hover */\n\ninput:hover,\nselect:hover {\n    box-shadow: 0 0 8px fade(@black, (0.3 * 100));\n}\n\n/* App Store Badges */\n\n[class*='badge-'] {\n    background-color: @black;\n    border: 1px solid #345;\n}\n\nform .flexblock li:hover {\n    background-color: fade(@black, (0.05 * 100));\n}\n\n/*============================\nTable of Contents\n============================== */\n\n.toc,\n.toc ol > li:before,\n.chapter {\n    background-color: #f7f9fb;\n}\n\n.toc li .toc-page:before {\n    border-bottom: 1px dotted fade(@black, (0.9 * 100));\n}\n\n/*============================\nSlides (Counter/Arrows)\n============================== */\n\n#counter,\n#navigation a {\n    color: #abc;\n}\n\n#webslides:hover #navigation a:hover {\n    background-color: @index-overlay;\n    color: @white;\n}\n\n/*============================\nFooter\n============================== */\n\nfooter[role='contentinfo'] {\n    background-color: @white;\n}\n\n/*============================\nSlides Index\n============================== */\n\n#webslides-zoomed {\n    background: @index-overlay;\n}\n\n#webslides-zoomed .column > .wrap-zoom {\n    background-color: @catskill-white;\n    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 4px 8px rgba(0, 0, 0, 0.04);\n    color: @mine-shaft;\n    &:hover {\n        box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 4px 8px rgba(0, 0, 0, 0.08);\n    }\n    &.current {\n        border: 0.6rem solid rgba(0, 20, 280, 0.2);\n    }\n}\n\n.text-slide-number {\n    color: #abc;\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/_typography.less",
    "content": "/*============================\n2. TYPOGRAPHY & LISTS\n============================== */\nhtml,\nbody {\n    line-height: 1;\n    text-rendering: optimizeLegibility;\n}\n\nhtml,\nbody,\ninput,\nselect,\ntextarea {\n    font-family: -apple-system, 'Noto Sans', SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, monospace,\n        'Helvetica Neue', Helvetica, 'Nimbus Sans L', Arial, 'Liberation Sans', 'PingFang SC', 'Hiragino Sans GB',\n        'Noto Sans CJK SC', 'Source Han Sans SC', 'Source Han Sans CN', 'Microsoft YaHei', 'Wenquanyi Micro Hei',\n        'WenQuanYi Zen Hei', 'ST Heiti', SimHei, 'WenQuanYi Zen Hei Sharp', sans-serif;\n    font-size: 62.5%;\n}\n\nbody,\ntextarea {\n    font-size: 1.8rem;\n}\n\np,\nli,\ndt,\ndd,\ntime,\ntable,\nbig,\ntextarea,\nlabel {\n    line-height: 3.2rem;\n    margin-bottom: 3.2rem;\n}\n\nli,\np:last-child {\n    margin-bottom: 0;\n}\n\nul > li,\nol > li {\n    margin-left: 3.2rem;\n}\n\nli li {\n    font-size: 100%;\n}\n\n/*== List .description (Product/Specs) === */\nul.description {\n    padding: 0;\n\n    & + p {\n        margin-top: 3.2rem;\n    }\n\n    li {\n        padding-bottom: 0.8rem;\n        padding-top: 0.8rem;\n        position: relative;\n        transition: 0.3s;\n    }\n\n    li:hover {\n        padding-left: 0.4rem;\n    }\n}\n\nul.description li,\n.column ul li {\n    list-style: none;\n    margin-left: 0;\n}\n\n.column ol > li {\n    margin-left: 1.6rem;\n}\n\nh1 svg,\nh2 svg,\nh3 svg,\nh4 svg {\n    margin-top: -0.8rem;\n}\n\n.text-intro svg,\n.text-quote p svg,\n.wall p svg,\n.try svg {\n    margin-top: -0.4rem;\n}\n\nh1 {\n    font-size: 4rem;\n    line-height: 5.6rem;\n\n    @media (min-width: 768px) {\n        font-size: 5.6rem;\n        line-height: 7.2rem;\n    }\n}\n\nh1 span {\n    font-style: italic;\n}\n\nh2 {\n    font-size: 3.2rem;\n    line-height: 4.8rem;\n\n    @media (min-width: 768px) {\n        font-size: 4.8rem;\n        line-height: 6.4rem;\n    }\n}\n\nh3 {\n    font-size: 2.4rem;\n    line-height: 4rem;\n\n    @media (min-width: 768px) {\n        font-size: 4rem;\n        line-height: 5.6rem;\n    }\n}\n\nh4 {\n    font-size: 2.2rem;\n    line-height: 4rem;\n\n    @media (min-width: 768px) {\n        font-size: 3.2rem;\n        line-height: 4.8rem;\n    }\n}\n\nh5 {\n    font-size: 2rem;\n    font-weight: 600;\n    line-height: 3.2rem;\n}\n\nh6 {\n    font-size: 1.8rem;\n    font-weight: 600;\n    line-height: 3.2rem;\n}\n\nh2.alignleft + p.alignright {\n    margin-bottom: 0;\n    margin-top: 1.2rem;\n}\n\nh3.alignleft + p.alignright {\n    margin-bottom: 0;\n    margin-top: 0.4rem;\n}\n\n\n\neach(range(6), {\n    h@{value} + h1,\n    h@{value} + h2,\n    h@{value} + h3,\n    h@{value} + h4,\n    h@{value} + h5,\n    h@{value} + h6 {\n        margin-top: 0.8rem;\n    }\n});\n\nh1 + img,\nh2 + img,\nh3 + img {\n    margin-bottom: 4.8rem;\n    margin-top: 4.8rem;\n}\n\n[class*='content-'] > [class*='content-'] h2,\n[class*='content-'] > [class*='content-'] h3,\n[class*='content-'] > [class*='content-'] h4 {\n    font-size: 2.4rem;\n    line-height: 4rem;\n}\n\n/*== 2.1. Headings with background ==*/\n\n\neach(range(6), {\n    h@{value}[class*='bg-'] {\n        padding: 2.4rem;\n    }\n});\n\nul[class*='bg-'],\nol[class*='bg-'],\nli[class*='bg-'],\np[class*='bg-'] {\n    padding: 2.4rem;\n}\n\nh1 [class*='bg-'],\nh2 [class*='bg-'],\nh3 [class*='bg-'] {\n    padding: 0.4rem 0.8rem;\n}\n\n/*== 2.2. Typography Classes = .text- == */\n.text-intro,\n[class*='content-'] p {\n    font-size: 2.4rem;\n    line-height: 4rem;\n}\n\n/* -- Serif -- */\n.text-serif,\nh1 span {\n    font-family: 'Maitree', times, serif;\n}\n\n/* -- h1,h2... Promo/Landings -- */\n.text-landing {\n    letter-spacing: 0.4rem;\n    text-transform: uppercase;\n\n    @media (min-width: 768px) {\n        letter-spacing: 1.6rem;\n    }\n}\n\n/* -- Subtitle (Before h1, h2) p.subtitle + h1/h2 */\n.text-subtitle {\n    letter-spacing: 0.2rem;\n    margin-bottom: 0;\n    text-transform: uppercase;\n\n    p& {\n        font-size: 1.6rem;\n\n        svg {\n            vertical-align: text-top;\n        }\n    }\n\n    + p {\n        margin-top: 3.2rem;\n    }\n}\n\n.text-uppercase {\n    text-transform: uppercase;\n}\n\n.text-lowercase {\n    text-transform: lowercase;\n}\n\n/* -- Emoji (you'll love this) -- */\n.text-emoji {\n    font-size: 6.8rem;\n    line-height: 8.8rem;\n\n    @media (min-width: 768px) {\n        font-size: 12.8rem;\n        line-height: 16rem;\n    }\n}\n\n/* -- Numbers (results, sales... 23,478,289 iphones) -- */\n.text-data {\n    font-size: 6.4rem;\n    line-height: 8rem;\n    margin-bottom: 0.8rem;\n\n    @media (min-width: 768px) {\n        font-size: 15.2rem;\n        line-height: 16.8rem;\n    }\n}\n\n.text-label {\n    display: inline-block;\n    font-weight: 600;\n    text-transform: uppercase;\n    width: 12.8rem;\n}\n\n/* -- Magazine Two Columns -- */\n@media (min-width: 768px) {\n    .text-cols {\n        column-count: 2;\n        column-gap: 4.8rem;\n        text-align: left;\n    }\n\n    .text-landing + .text-cols {\n        margin-top: 3.2rem;\n    }\n}\n\n.text-cols p:first-child:first-letter {\n    float: left;\n    font-size: 11rem;\n    font-weight: 600;\n    line-height: 1;\n    margin: -0.4rem 1.6rem 0 0;\n    padding: 0;\n    text-transform: uppercase;\n}\n\n/* -- Heading with border -- */\n.text-context {\n    position: relative;\n\n    &:before {\n        content: '';\n        display: block;\n        height: 0.2rem;\n        margin-bottom: 0.6rem;\n        width: 12rem;\n\n        .column & {\n            width: 100%;\n        }\n    }\n\n    &.text-uppercase {\n        letter-spacing: 0.1rem;\n    }\n}\n\n/* -- Separator/Symbols (stars ***...) -- */\n.text-symbols {\n    font-weight: 600;\n    letter-spacing: 0.8rem;\n    text-align: center;\n}\n\n.text-separator {\n    margin-top: 2.4rem;\n\n    &:before {\n        content: '';\n        height: 0.4rem;\n        left: 0;\n        margin-top: -1.6rem;\n        position: absolute;\n        width: 16%;\n    }\n\n    @media (min-width: 568px) {\n        margin-left: 20%;\n        margin-top: 0;\n        width: 80%;\n\n        &:before {\n            margin-top: 1.2rem;\n        }\n    }\n}\n\n/* -- Pull Quote (Right/Left)  -- */\n[class*='text-pull'] {\n    font-size: 2.4rem;\n    font-weight: 400;\n    line-height: 4rem;\n    margin-bottom: 3.2rem;\n    margin-left: 2.4rem;\n    margin-right: 2.4rem;\n    position: relative;\n}\n\n[class*='text-pull-'] {\n    margin-top: 0.8rem;\n    padding-top: 1.4rem;\n\n    @media (min-width: 1024px) {\n        margin-left: -4.8rem;\n        margin-right: -4.8rem;\n    }\n}\n\n@media (min-width: 568px) {\n    [class*='text-pull-'] {\n        max-width: 40%;\n    }\n\n    .text-pull-right {\n        float: right;\n        margin-left: 2.4rem;\n        margin-right: -2.4rem;\n    }\n\n    .text-pull-left {\n        float: left;\n        margin-left: -2.4rem;\n        margin-right: 2.4rem;\n    }\n}\n\nimg[class*='text-pull-'],\nfigure[class*='text-pull-'] {\n    margin-top: 0.8rem;\n    padding-top: 0;\n}\n\n/* -- Interviews (Questions & Answers)  --- */\n/* -- <dl class=\"text-interview\">\n<dt>name</dt>\n<dd><p>question or answer</p>\n</dd>\n--- */\n.text-interview dt {\n    font-weight: 600;\n    margin-bottom: 0;\n    text-transform: uppercase;\n}\n\n@media (min-width: 1024px) {\n    .text-interview dt {\n        margin-left: -34%;\n        position: absolute;\n        text-align: right;\n        white-space: nowrap;\n        width: 30%;\n    }\n}\n\n/* -- Info Messages (error, warning, success... -- */\n.text-info {\n    font-size: 1.6rem;\n    line-height: 2.4rem;\n}\n\n/*=========================================\n2.1. San Francisco Font (Apple's new font)\n=========================================== */\n.text-apple,\n.bg-apple {\n    font-family: 'San Francisco', helvetica, arial, sans-serif;\n}\n\n/* Ultra Light */\n@font-face {\n    font-family: 'San Francisco';\n    font-weight: 100;\n    src: url('https://applesocial.s3.amazonaws.com/assets/styles/fonts/sanfrancisco/sanfranciscodisplay-ultralight-webfont.woff2');\n}\n\n/* Thin */\n@font-face {\n    font-family: 'San Francisco';\n    font-weight: 200;\n    src: url('https://applesocial.s3.amazonaws.com/assets/styles/fonts/sanfrancisco/sanfranciscodisplay-thin-webfont.woff2');\n}\n\n/* Regular */\n@font-face {\n    font-family: 'San Francisco';\n    font-weight: 400;\n    src: url('https://applesocial.s3.amazonaws.com/assets/styles/fonts/sanfrancisco/sanfranciscodisplay-regular-webfont.woff2');\n}\n\n/* Bold */\n@font-face {\n    font-family: 'San Francisco';\n    font-weight: bold;\n    src: url('https://applesocial.s3.amazonaws.com/assets/styles/fonts/sanfrancisco/sanfranciscodisplay-bold-webfont.woff2');\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/_vars.less",
    "content": "// sass-lint:disable no-color-literals\n\n// =========\n// Colors. Names from http://chir.ag/projects/name-that-color/\n// =========\n@black: #000;\n@white: #fff;\n@mine-shaft: #333;\n@royal-blue: #44d;\n@havelock-blue: #67d;\n@catskill-white: #f7f9fb;\n@cod-gray: #111;\n@big-stone: #123;\n@rhino: #346;\n@athens-gray: #f8f8f9;\n@mischka: #d5d9e2;\n@pine-green: #077;\n@purple-heart: #62b;\n@cardinal: #c23;\n@mirage: #1a2028;\n@pickled-bluewood: #293845;\n@facebook: #3b5998;\n@spindle: #bce;\n@dodger-blue: #3af;\n@pattens-blue: #def;\n@stratos: #001450;\n@gray-brown: #f9f8f2;\n\n\n@body-color: @mine-shaft;\n@body-bg: @catskill-white;\n@focus-box-shadow: 0 0 2px rgba(150, 187, 238, 1);\n@link-color: @royal-blue;\n@link-color-secondary: @cardinal;\n@link-hover: @dodger-blue;\n@hr-bg: radial-gradient(ellipse at center, rgba(0, 20, 80, 0.2) 0, rgba(255, 255, 255, 0) 75%);\n@current-zoomed-slide-shadow: 0 0 7px rgba(0, 187, 255, 0.5);\n@index-overlay: rgba(0, 10, 40, 0.8);\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/full.less",
    "content": "/*-----------------------------------------------------------------------------------\n\t0. CSS Reset & Normalize\n\t1. Base\n\t\t1.1 Wrap/Container\n\t\t1.2 Animations\n\t\t1.3 Responsive Media (videos, iframe, screenshots...)\n\t\t1.4 Basic Grid (2,3,4 columns)\n\t2. Typography & Lists\n\t\t2.1 Headings with background\n\t\t2.2 Classes: .text-\n\t\t2.3 San Francisco Font (Apple)\n\t3. Header & Footer\n\t\t3.1 Logo\n\t4. Navigation\n\t\t4.1 Navbars\n\t5. SLIDES (vertically and horizontally centered)\n\t\t5.1 Mini container & Alignment\n\t\t5.2 Counter / Navigation Slides\n\t\t5.3 Background Images/Video\n\t6. Magic blocks = .flexblock (Flexible blocks with auto-fill and equal height).\n\t\t6.1 .flexblock.features\n\t\t6.2 .flexblock.clients\n\t\t6.3 .flexblock.steps\n\t\t6.4 .flexblock.metrics\n\t\t6.5 .flexblock.specs\n\t\t6.6 .flexblock.reasons\n\t\t6.7 .flexblock.gallery\n\t\t6.8 .flexblock.plans\n\t\t6.9. flexblock.activity\n\t7. Promos/Offers (pricing, tagline, CTA...)\n\t8. Work / Resume / CV\n\t9. Table of contents\n\t10. Cards\n\t11. Quotes\n\t12. Avatars\n\t13. Tables\n\t14. Forms\n  15. Longform Elements\n\t16. Safari Bug (flex-wrap)\n\t17. Slidex index (aka zoom)\n  18. Print\n  19. Colors\n----------------------------------------------------------------------------------- */\n\n@import (optional) '_vars.less';\n@import (optional) 'utils/_reset.less';\n@import (optional) 'utils/_clear.less';\n@import (optional) '_base.less';\n@import (optional) 'utils/_animations.less';\n@import (optional) 'modules/_media.less';\n@import (optional) 'modules/_browser.less';\n@import (optional) 'modules/_grid.less';\n@import (optional) '_typography.less';\n@import (optional) 'modules/_header-footer.less';\n// @import (optional) \"modules/logo.less\";\n// @import (optional) 'modules/_logo.less';\n@import (optional) 'modules/_navigation.less';\n@import (optional) 'modules/_slides.less';\n@import (optional) 'modules/_slides-bg.less';\n@import (optional) 'modules/_slides-navigation.less';\n@import (optional) 'modules/_flexblock.less';\n@import (optional) 'modules/_flexblock-features.less';\n@import (optional) 'modules/_flexblock-clients.less';\n@import (optional) 'modules/_flexblock-steps.less';\n@import (optional) 'modules/_flexblock-metrics.less';\n@import (optional) 'modules/_flexblock-specs.less';\n@import (optional) 'modules/_flexblock-reasons.less';\n@import (optional) 'modules/_flexblock-gallery.less';\n@import (optional) 'modules/_flexblock-plans.less';\n@import (optional) 'modules/_flexblock-activity.less';\n@import (optional) 'modules/_promos.less';\n@import (optional) 'modules/_work.less';\n@import (optional) 'modules/_toc.less';\n@import (optional) 'modules/_cards.less';\n@import (optional) 'modules/_quotes.less';\n@import (optional) 'modules/_avatars.less';\n@import (optional) 'modules/_tables.less';\n@import (optional) 'modules/_form.less';\n@import (optional) 'modules/_button.less';\n@import (optional) \"modules/_badges.less\";\n@import (optional) 'modules/_longform.less';\n@import (optional) 'utils/_bugs.less';\n@import (optional) 'modules/_zoom.less';\n@import (optional) 'modules/_print.less';\n// speaker mode\n@import (optional) 'modules/_with-note.less';\n@import (optional) '_color.less';\n\n// speaker-note\n@import (optional) 'modules/_speaker-note.less';\n@import (optional) 'modules/_animation.less';\n\n// build item\n@import (optional) 'modules/_build.less';\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/index.less",
    "content": "#webslides {\n    opacity: 0;\n}\n\n.ws-ready #webslides {\n    opacity: 1;\n}\n\ni.fa {\n    display: inline-block;\n    vertical-align: middle;\n    width: 1em;\n    height: 1em;\n}\n\n[class*='fa-'].large {\n    font-size: 8rem;\n}\n\n.embed .echarts,\n.embed .lang-mermaid {\n    min-height: 20vw;\n    > div {\n        margin: 0 auto;\n    }\n}\n.embed .lang-mermaid {\n    visibility: hidden;\n    text-align: center;\n}\n.animated.delay-300 {\n    animation-delay: 0.3s;\n}\n\n.animated.delay-400 {\n    animation-delay: 0.4s;\n}\n\n.animated.delay-500 {\n    animation-delay: 0.5s;\n}\n\n.animated.delay-600 {\n    animation-delay: 0.6s;\n}\n\n.animated.delay-700 {\n    animation-delay: 0.7s;\n}\n\n.animated.delay-800 {\n    animation-delay: 0.8s;\n}\n\n.animated.delay-900 {\n    animation-delay: 0.9s;\n}\n\n.animated.delay-1100 {\n    animation-delay: 1.1s;\n}\n\n.animated.delay-1200 {\n    animation-delay: 1.2s;\n}\n\n.animated.delay-1300 {\n    animation-delay: 1.3s;\n}\n\n.animated.delay-1400 {\n    animation-delay: 1.4s;\n}\n\n.animated.delay-1500 {\n    animation-delay: 1.5s;\n}\n\n.animated.delay-1600 {\n    animation-delay: 1.6s;\n}\n\n.animated.delay-1700 {\n    animation-delay: 1.7s;\n}\n\n.animated.delay-1800 {\n    animation-delay: 1.8s;\n}\n\n.animated.delay-2400 {\n    animation-delay: 2.4s;\n}\n\n.animated.delay-2500 {\n    animation-delay: 2.5s;\n}\n.animated.delay-2800 {\n    animation-delay: 2.8s;\n}\n.animated.delay-3200 {\n    animation-delay: 3.2s;\n}\n.animated.delay-3600 {\n    animation-delay: 3.6s;\n}\n.animated.delay-4s {\n    animation-delay: 4s;\n}\nsection ul {\n    display: inline-block;\n    text-align: left;\n    list-style: disc;\n    &.no-list-style {\n        list-style: none;\n    }\n    & > li {\n        margin-bottom: 0.5em;\n        ul {\n            margin-top: 0.5em;\n            display: block;\n        }\n        ul > li {\n            list-style-type: circle;\n        }\n    }\n}\n\n.flexblock li {\n    a.bg-trans-light,\n    a.bg-red,\n    a.bg-green,\n    a.bg-blue,\n    a.bg-purple,\n    a.bg-trans-dark,\n    a.bg-primary,\n    a.bg-secondary,\n    a.bg-black,\n    a.bg-black-blue {\n        color: white;\n    }\n}\n\n.badge-ios {\n    background-image: url('../images/bt-appstore.png');\n}\n\n.badge-android {\n    background-image: url('../images/bt-playstore.png');\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_animation.less",
    "content": "// 翻页效果\n\n.slide.pagedown[data-transition='slide'].past {\n    animation: slideOutScaleLeft 1s forwards cubic-bezier(1, 0, 0, 1);\n}\n\n.slide.pagedown[data-transition='slide'].current {\n    opacity: 1;\n    animation: slideInFromRight 1s forwards cubic-bezier(1, 0, 0, 1);\n}\n\n.slide.pageup[data-transition='slide'].current {\n    opacity: 1;\n    animation: slideInFromLeft 1s forwards cubic-bezier(1, 0, 0, 1);\n}\n\n.slide.pageup[data-transition='slide'].next {\n    opacity: 1;\n    animation: slideOutScaleRight 1s forwards cubic-bezier(1, 0, 0, 1);\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_avatars.less",
    "content": "/*=========================================\n12. Avatars - uifaces.com\n=========================================== */\n\ncite img,\nimg[class*='avatar-'] {\n    display: inline-block;\n    margin-right: 6px;\n    vertical-align: middle;\n}\n\nimg[class*='avatar-'] {\n    border-radius: 50%;\n}\n\n@avatar-sizes: 40, 48, 56, 64, 72, 80;\neach(@avatar-sizes, {\n    img.avatar-@{value} {\n        @width: @{value}px;\n        height: @width;\n        width: @width;\n    }\n});\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_badges.less",
    "content": "/*=== App Store Badges === */\n/* Change width and height: 216x64px, 162x48px, 135x40... */\n\n[class*='badge-'] {\n    background-repeat: no-repeat;\n    background-size: cover;\n    border-radius: 0.6rem;\n    display: inline-block;\n    height: 40px;\n    line-height: 4rem;\n    text-indent: -4000px;\n    width: 135px;\n\n    &:hover {\n        opacity: 0.7;\n    }\n\n    @media (min-width: 1024px) {\n        height: 48px;\n        line-height: 4.8rem;\n        width: 162px;\n    }\n\n    @media (min-width: 500px) {\n        & + & {\n            margin-left: 1.8rem;\n        }\n    }\n\n    @media (max-width: 499px) {\n        & + & {\n            margin-top: 0.8rem;\n        }\n    }\n}\n\n\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_browser.less",
    "content": "/*=== HTML Browser (Screenshots) ================ */\n/* <figure class=\"browser\"> img </figure> */\n\n.browser {\n    border-radius: 0.3rem;\n    margin: 0 auto 3.2rem;\n    max-width: 1024px;\n    overflow: hidden;\n\n    li & {\n        margin-bottom: 0;\n    }\n\n    h1 + &,\n    h2 + &,\n    p + & {\n        margin-top: 4.8rem;\n    }\n\n    figcaption {\n        padding: 2.4rem;\n    }\n\n    &:before {\n        content: '● ● ●';\n        font-size: 0.8rem;\n        left: 0;\n        line-height: 0;\n        padding: 1.6rem;\n        position: absolute;\n        text-align: left;\n        top: 0;\n        width: 100%;\n\n        @media (min-width: 768px) {\n            font-size: 1.6rem;\n        }\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_build.less",
    "content": "/* Builds */\n\n.build.fadeIn {\n    > * {\n        transition: opacity 0.5s ease-in-out 0.2s;\n    }\n    .tobuild {\n        opacity: 0;\n    }\n}\n\n.zoomIn > * {\n    animation: none;\n}\n\n.zoomIn {\n    > * {\n        opacity: 1;\n    }\n    .tobuild {\n        opacity: 0;\n    }\n    .animated {\n        opacity: 1;\n        animation-duration: 0.75s;\n        animation-fill-mode: both;\n    }\n}\n\n.zoomIn {\n    .animated {\n        animation-name: zoomIn;\n    }\n}\n\n.moveIn {\n    > * {\n        transition: all 0.5s ease-in-out 0.2s;\n    }\n    .tobuild {\n        opacity: 0;\n        transform: translate3d(60px, 0, 0);\n    }\n}\n\n.rollIn {\n    > * {\n        transition: all 0.4s ease;\n    }\n    .animated {\n        opacity: 1;\n        visibility: visible;\n        transform: rotateX(0);\n    }\n    .tobuild {\n        opacity: 0;\n        visibility: hidden;\n        transform: rotateX(90deg);\n    }\n}\n\nsection .build,\nsection .tobuild {\n    animation: none;\n}\n\nsection .tobuild {\n    opacity: 0;\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_button.less",
    "content": "/* Buttons/Badges */\n[class*='button'] {\n    @media (min-width: 500px) {\n        & + & {\n            margin-left: 1.8rem;\n        }\n    }\n\n    @media (max-width: 499px) {\n        & + & {\n            margin-top: 0.8rem;\n        }\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_cards.less",
    "content": "/*===========================================\n10. Cards\n============================================= */\n\n[class*='card-'] {\n    &,\n    & > a {\n        clear: both;\n        display: flex;\n        flex-direction: row;\n        position: relative;\n    }\n\n    .fullscreen &,\n    .fullscreen & > a {\n        min-height: 100vh;\n    }\n\n    figure img,\n    figure iframe {\n        display: block;\n        margin: 0 auto;\n    }\n\n    figure figcaption {\n        bottom: 0;\n        font-size: 1.4rem;\n        left: 0;\n        line-height: 2.4rem;\n        padding: 0.8rem 2.4rem;\n        position: absolute;\n        z-index: 2;\n\n        svg {\n            font-size: 1rem;\n        }\n    }\n}\n\n@media (min-width: 768px) {\n    [class*='card'][class*='bg-'] figure,\n    .fullscreen [class*='card'] figure {\n        max-height: 100%;\n        min-width: 380px;\n        text-align: center;\n        vertical-align: middle;\n    }\n\n    [class*='card-'][class*='bg-'] figure img,\n    [class*='card-'][class*='bg-'] figure iframe,\n    .fullscreen [class*='card-'] figure img,\n    .fullscreen [class*='card-'] figure iframe {\n        height: 100%;\n        left: 0;\n        object-fit: cover;\n        position: absolute;\n        top: 0;\n        width: 100%;\n        z-index: 1;\n    }\n}\n\n.flex-content,\n[class*='card'] blockquote {\n    padding: 2.4rem;\n    position: relative;\n}\n\n[class*='card-'] .flex-content,\n[class*='card-'] blockquote {\n    display: flex;\n    flex-direction: column;\n    justify-content: center;\n}\n\n.flex-content p {\n    position: relative;\n}\n\n@media (min-width: 768px) {\n    .card-50 figure,\n    .card-50 blockquote,\n    .card-50 .flex-content {\n        width: 50%;\n    }\n\n    .card-30 figure,\n    .card-70 .flex-content,\n    .card-70 blockquote {\n        width: 30%;\n    }\n\n    .card-40 figure,\n    .card-60 .flex-content,\n    .card-60 blockquote {\n        width: 40%;\n    }\n\n    .card-60 figure,\n    .card-40 .flex-content,\n    .card-40 blockquote {\n        width: 60%;\n    }\n\n    .card-70 figure,\n    .card-30 .flex-content,\n    .card-30 blockquote {\n        width: 70%;\n    }\n\n    [class*='card']:nth-child(odd) figure {\n        order: 0;\n    }\n\n    [class*='card']:nth-child(even) figure {\n        order: 1;\n    }\n\n    .flex-content,\n    [class*='card'] blockquote {\n        padding: 4.8rem;\n    }\n\n    .fullscreen [class*='card'] .flex-content,\n    .fullscreen [class*='card'] blockquote {\n        padding: 6.4rem;\n    }\n}\n\n@media (max-width: 767px) {\n    [class*='card-'],\n    [class*='card-'] > a {\n        flex-flow: column;\n    }\n\n    .card figure,\n    .card header {\n        width: 100%;\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_flexblock-activity.less",
    "content": "/*===========================================\n6.9 Block Activity <ul class=\"activity\">\nCV / News\n============================================= */\n\n.flexblock.activity {\n    flex-direction: column;\n\n    li {\n        flex: 1;\n        position: relative;\n        width: auto;\n    }\n\n    p {\n        margin-bottom: 0;\n        vertical-align: top;\n    }\n\n    img {\n        display: block;\n    }\n\n    .year,\n    .title {\n        display: inline;\n        font-weight: 600;\n    }\n\n    .summary {\n        width: 100%;\n    }\n\n    .title {\n        margin-left: 1rem;\n    }\n\n    @media (min-width: 768px) {\n        p {\n            float: left;\n        }\n\n        .year {\n            width: 15%;\n        }\n\n        .title {\n            margin-left: 4%;\n            margin-right: 4%;\n            width: 27%;\n        }\n\n        .summary {\n            width: 50%;\n        }\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_flexblock-clients.less",
    "content": "/*=====================================================================\n6.2 Clients Logos <ul class=\"flexblock clients\">\n======================================================================= */\n\n.flexblock.clients.blink li > a,\n.flexblock.clients li {\n    padding: 0;\n}\n\n.flexblock.clients li figcaption {\n    padding: 0 2.4rem 2.4rem;\n}\n\n.flexblock.clients.border li figcaption {\n    padding-top: 2.4rem;\n}\n\n.clients.blink li > a,\n.clients li {\n    justify-content: inherit;\n}\n\n.clients li img,\n.clients li svg {\n    display: block;\n    padding: 2.4rem;\n}\n\n.clients.border li img,\n.clients.border li svg {\n    display: block;\n    margin-left: auto;\n    margin-right: auto;\n}\n\n.clients li:hover {\n    z-index: 1;\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_flexblock-features.less",
    "content": "/*====================================================================\n6.1 Features <ul class=\"flexblock features\">\n====================================================================== */\n\n.flexblock.features {\n    > li {\n        border-radius: 0.4rem;\n        margin-bottom: 4.8rem;\n        width: 100%;\n    }\n    li h2 {\n        text-transform: uppercase;\n    }\n    li span {\n        font-weight: 300;\n    }\n    li p {\n        margin: 0;\n    }\n    li p em {\n        display: block;\n    }\n    li span,\n    li svg,\n    li i.fa {\n        display: block;\n        font-size: 6.4rem;\n        line-height: 1;\n        margin: 0;\n    }\n    li img {\n        width: 6.4rem;\n    }\n    li span sup {\n        font-size: 3rem;\n    }\n    @media (min-width: 1200px) {\n        li span,\n        li svg,\n        li i.fa,\n        li img {\n            float: left;\n            margin-right: 0.8rem;\n        }\n        li i.fa {\n            margin-right: 1.8rem;\n        }\n    }\n}\n\n@media (min-width: 768px) {\n    .flexblock.features {\n        margin-left: -2%;\n        margin-right: -2%;\n    }\n    .flexblock.features > li {\n        margin-left: 2%;\n        margin-right: 2%;\n        width: 29%;\n    }\n    .size-50 .flexblock.features > li {\n        width: 46%;\n    }\n    .column .flexblock.features > li {\n        width: 100%;\n    }\n    footer .flexblock.features > li {\n        margin-bottom: 0;\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_flexblock-gallery.less",
    "content": "/*=================================================\n6.7 Gallery - <ul class=\"flexblock gallery\">\nBlock Thumbnails li+.overlay+image\nimg size recommended:800x600px\n=================================================== */\n\n.flexblock.gallery {\n    li {\n        margin-bottom: 4.8rem;\n\n        &:nth-child(n + 4) {\n            flex: inherit;\n        }\n\n        figcaption {\n            padding: 1.6rem;\n            position: relative;\n\n            &:before {\n                content: '';\n                height: 0;\n                left: 20%;\n                margin-left: -0.5em;\n                position: absolute;\n                top: 0.4rem;\n                transform: rotate(135deg);\n                transform-origin: 0 0;\n                transition: 0.1s;\n                width: 0;\n            }\n        }\n\n        &:hover figcaption:before {\n            top: 0.3rem;\n        }\n    }\n\n    .aligncenter & li figcaption:before {\n        left: 55%;\n        margin-left: 0;\n    }\n\n    li,\n    &.blink li > a {\n        padding: 0;\n    }\n\n    h2 {\n        text-transform: uppercase;\n    }\n\n    h2 + p,\n    h3 + p {\n        margin-top: 0.8rem;\n    }\n\n    p {\n        font-size: 1.6rem;\n        line-height: 2.4rem;\n        margin-bottom: 0;\n    }\n\n    li footer {\n        margin-top: 0.8rem;\n        padding: 1.2rem 0 0;\n        position: relative;\n    }\n\n    li img {\n        display: block;\n        margin-left: auto;\n        margin-right: auto;\n    }\n\n    @media (min-width: 600px) {\n        margin-left: -2%;\n        margin-right: -2%;\n\n        li {\n            margin-left: 2%;\n            margin-right: 2%;\n            width: 46%;\n        }\n    }\n}\n\n@media (min-width: 1024px) {\n    .flexblock.gallery li {\n        width: 21%;\n    }\n\n    .grid.sm .flexblock.gallery li,\n    .grid.ms .flexblock.gallery li {\n        width: 29%;\n    }\n\n    .grid.sms .flexblock.gallery li {\n        width: 46%;\n    }\n}\n\n.overlay {\n    bottom: 0;\n    cursor: pointer;\n    display: flex;\n    flex-direction: column;\n    height: 100%;\n    justify-content: center;\n    left: 0;\n    opacity: 1;\n    position: absolute;\n    right: 0;\n    top: 0;\n    transition: all 0.3s linear;\n    width: 100%;\n    z-index: 2;\n}\n\nli .overlay {\n    align-items: center;\n}\n\nli .overlay h2 {\n    letter-spacing: 0.2rem;\n    margin: 0;\n    padding: 0 2.4rem;\n    text-align: center;\n    text-transform: uppercase;\n    width: 100%;\n}\n\n.overlay p,\n.overlay time {\n    margin-bottom: 0;\n}\n\nli:hover .overlay {\n    cursor: pointer;\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_flexblock-metrics.less",
    "content": "/*=================================================\n6.4 Block Numbers - <ul class=\"flexblock metrics\">\n=================================================== */\n\n.metrics li {\n    text-align: center;\n    width: 100%;\n\n    @media (min-width: 568px) {\n        width: 50%;\n    }\n\n    @media (min-width: 1024px) {\n        width: 25%;\n    }\n}\n\n.metrics li strong {\n    display: block;\n}\n\n.metrics li span,\n.metrics li i,\n.metrics li svg {\n    display: block;\n    font-size: 6.4rem;\n    line-height: 7.2rem;\n    margin: 0 auto;\n}\n\n.card-50 .metrics li {\n    width: 50%;\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_flexblock-plans.less",
    "content": "/*===============================================\n6.8 Plans / Pricing <ul class=\"flexblock plans\">\n================================================= */\n\n.flexblock.plans {\n    > li {\n        border-radius: 3px;\n        margin-bottom: 4.8rem;\n        text-align: center;\n        z-index: 1;\n    }\n\n    li,\n    &.blink li > a {\n        padding: 0;\n    }\n\n    &.blink li > a div,\n    li div {\n        padding-bottom: 3.2rem;\n    }\n\n    li p,\n    li h2 {\n        padding: 0.8rem 3.2rem;\n    }\n\n    li h2 {\n        float: left;\n        font-weight: 400;\n        letter-spacing: 0.1rem;\n        text-transform: uppercase;\n        width: 100%;\n    }\n\n    .price {\n        clear: both;\n        display: block;\n        font-size: 4.8rem;\n        font-weight: 400;\n        line-height: 6.2rem;\n        padding: 2.4rem;\n\n        sup {\n            font-size: 1.8rem;\n            margin-right: 0.4rem;\n        }\n\n        li ul {\n            margin-bottom: 2.4rem;\n        }\n    }\n\n    li ul li {\n        display: block;\n        padding: 0.8rem 3.2rem;\n        text-align: left;\n        width: 100%;\n    }\n\n    @media (min-width: 1024px) {\n        margin-left: -2%;\n        margin-right: -2%;\n\n        > li {\n            margin-left: 2%;\n            margin-right: 2%;\n            width: 29%;\n        }\n\n        > li:hover,\n        > li:nth-child(2) {\n            position: relative;\n            transform: scale(1.08);\n            z-index: 2;\n        }\n\n        &:hover li:nth-child(2):not(:hover) {\n            position: relative;\n            transform: scale(1);\n            z-index: 1;\n        }\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_flexblock-reasons.less",
    "content": "/*=================================================\n6.6 Reasons/Why/Numbers (counter-increment)\n<ul class=\"flexblock reasons\">\n=================================================== */\n.flexblock.reasons {\n    li {\n        counter-increment: list;\n        text-align: left;\n        width: 100%;\n\n        &:hover {\n            transform: translateY(-0.2rem);\n        }\n\n        &:after {\n            bottom: -2.4rem;\n            content: '';\n            display: block;\n            height: 1px;\n            position: relative;\n        }\n\n        &:before {\n            content: counter(list) '.';\n            font-size: 6.4rem;\n            line-height: 1;\n        }\n\n        @media (min-width: 768px) {\n            padding-left: 8.8rem;\n            /* You need two digits? (1-10)*/\n            /*padding-left: 12rem; */\n\n            &:before {\n                left: 2.4rem;\n                position: absolute;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_flexblock-specs.less",
    "content": "/*=====================================================\n6.5 Specs/Items: <ul class=\"flexblock specs\">\n======================================================= */\n\n.specs li {\n    text-align: left;\n    width: 100%;\n\n    &:after {\n        bottom: -2.4rem;\n        content: '';\n        display: block;\n        height: 1px;\n        position: relative;\n    }\n\n    &:hover {\n        transform: translateX(0.2rem);\n    }\n\n    span,\n    i,\n    svg {\n        display: block;\n        font-size: 6.4rem;\n        line-height: 1;\n        margin: 0;\n    }\n\n    img {\n        width: 6.4rem;\n    }\n\n    span {\n        font-weight: 300;\n\n        sup {\n            font-size: 3rem;\n        }\n    }\n\n    @media (min-width: 1024px) {\n        span,\n        svg,\n        i,\n        img {\n            float: left;\n            margin-right: 2.4rem;\n        }\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_flexblock-steps.less",
    "content": "/*==================================================\n6.3 flexblock.steps <ul class=\"flexblock steps\">\nAbout, Philosophy...\n=================================================== */\n\n.steps li {\n    width: 100%;\n\n    img,\n    span {\n        display: block;\n        margin: 0 auto 0.8rem;\n    }\n\n    span {\n        font-size: 6.4rem;\n    }\n\n    @media (min-width: 768px) {\n        width: 50%;\n    }\n}\n\n@media (min-width: 1024px) {\n    .steps li {\n        width: 25%;\n    }\n\n    .process {\n        border-left-style: solid;\n        border-left-width: 15px;\n        height: 0;\n        left: 0;\n        position: absolute;\n        top: 60px;\n        width: 0;\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_flexblock.less",
    "content": "/*===============================================================\n6. Magic blocks with flexbox (Auto-fill & Equal Height)\nBlocks Links li>a = .flexblock.blink (.blink required)\n================================================================= */\n\n.flexblock {\n    clear: both;\n    display: flex;\n    flex-wrap: wrap;\n    margin-left: auto;\n    margin-right: auto;\n    padding: 0;\n\n    &:after {\n        clear: both;\n    }\n\n    &:before {\n        content: '';\n        display: table;\n    }\n\n    li,\n    &.blink li > a {\n        display: flex;\n        flex-direction: column;\n        margin: 0;\n        padding: 2.4rem;\n        position: relative;\n    }\n\n    li {\n        flex: auto;\n        text-align: left;\n        transition: 0.3s;\n        width: 100%;\n\n        &:hover {\n            transform: translateY(-0.2rem);\n        }\n\n        @media (min-width: 600px) {\n            width: 50%;\n        }\n\n        @media (min-width: 1024px) {\n            width: 25%;\n        }\n    }\n\n    &.aligncenter li {\n        text-align: center;\n    }\n\n    &.vertical-align li {\n        justify-content: center;\n    }\n\n    &.blink li {\n        padding: 0;\n    }\n\n    li h2 svg,\n    li h2 i,\n    li h3 i,\n    li h3 svg {\n        margin-top: 0;\n    }\n}\n\nh1 + .flexblock,\nh2 + .flexblock,\nh3 + .flexblock,\ndiv + ul,\ndiv + ol {\n    margin-top: 3.2rem;\n}\n\n.flexblock li h2,\n.flexblock li h3,\nfooter .column h2,\nfooter .column h3 {\n    font-size: 1.8rem;\n    font-weight: 600;\n    line-height: 3.2rem;\n    margin-bottom: 0;\n}\n\n.flexblock li li,\n.flexblock.blink li li {\n    padding: 0;\n    width: 100%;\n}\n\n[class*='content-'] .flexblock li p {\n    font-size: 1.8rem;\n    line-height: 3.2rem;\n}\n\n.content-right .flexblock.features li,\n.content-left .flexblock.features li {\n    width: 46%;\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_form.less",
    "content": "/*=========================================\n14. Forms\n=========================================== */\n\nform {\n    text-align: left;\n\n    & + p,\n    input + p,\n    textarea + p {\n        margin-top: 0.8rem;\n    }\n}\n\ninput[type='text'],\ninput[type='email'],\ninput[type='tel'],\ninput[type='url'],\ninput[type='search'],\ninput[type='password'] {\n    appearance: none;\n    border-radius: 0;\n}\n\ninput,\nbutton,\nselect {\n    display: inline-block;\n    font-size: 1.6rem;\n    font-weight: 400;\n    height: 4.8rem;\n    margin: 0;\n    padding: 0.7rem;\n    position: relative;\n    width: 100%;\n}\n\ninput[type='radio'],\ninput[type='checkbox'] {\n    height: auto;\n    padding: 4px;\n    width: auto;\n}\n\nbutton[type='submit'],\ntextarea {\n    width: 100%;\n}\n\ntextarea {\n    padding: 0.7rem;\n}\n\nbutton {\n    cursor: pointer;\n    text-align: center;\n    width: auto;\n}\n\n.button {\n    cursor: pointer;\n    display: inline-block;\n    font-size: 1.8rem;\n    font-weight: 400;\n    line-height: 4.8rem;\n    min-width: 16rem;\n    padding: 0 1.6rem;\n    text-align: center;\n\n    svg {\n        font-size: 2.4rem;\n    }\n}\n\n.button.radius,\ninput.radius {\n    border-radius: 2.4rem;\n}\n\nbutton,\ninput[type='submit'] {\n    font-weight: 400;\n    letter-spacing: 0.1rem;\n    text-transform: uppercase;\n}\n\n.plans .button {\n    margin-left: auto;\n    margin-right: auto;\n    width: 50%;\n}\n\n.try {\n    display: block;\n    font-size: 1.6rem;\n    margin-top: 1.6rem;\n}\n\nfieldset {\n    padding: 2.4rem;\n}\n\nlegend {\n    border: 0;\n    font-weight: 400;\n    letter-spacing: 0.1rem;\n    padding: 1.6rem 2.4rem;\n    text-align: center;\n    text-transform: uppercase;\n    width: 100%;\n}\n\ninput:focus,\ntextarea:focus,\nselect:focus {\n    border-width: 1px;\n}\n\na.button:hover,\nbutton[type='submit']:hover,\ninput[type='submit']:hover {\n    transform: scale(1.01);\n}\n\n:disabled,\nbutton:disabled:hover {\n    cursor: not-allowed;\n}\n\n.user {\n    input {\n        margin-bottom: 0;\n\n        &[type='email'],\n        &[type='search'],\n        &[type='text'] {\n            width: 100%;\n\n            @media (min-width: 500px) {\n                float: left;\n                width: 70%;\n            }\n        }\n    }\n\n    button,\n    input[type='submit'] {\n        left: 0;\n        width: 100%;\n\n        @media (min-width: 500px) {\n            cursor: pointer;\n            width: 30%;\n        }\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_grid.less",
    "content": "/*=== 1.4. Basic Grid (Flexible blocks)\nAuto-fill & Equal height === */\n\n.grid {\n    clear: both;\n    display: flex;\n    flex-wrap: wrap;\n    margin-left: auto;\n    margin-right: auto;\n\n    &:after {\n        clear: both;\n    }\n\n    &:before {\n        content: '';\n        display: table;\n    }\n\n    & > .column {\n        display: flex;\n        flex: auto;\n        flex-direction: column;\n        padding: 2.4rem;\n        position: relative;\n        transition: 0.3s;\n        width: 100%;\n    }\n\n    &.vertical-align .column {\n        justify-content: center;\n    }\n\n    @media (min-width: 768px) {\n        & > .column {\n            width: 25%;\n        }\n\n        &.sm .column:nth-child(1) {\n            width: 30%;\n        }\n\n        &.sm .column:nth-child(2) {\n            width: 70%;\n        }\n\n        &.ms .column:nth-child(1) {\n            width: 70%;\n        }\n\n        &.ms .column:nth-child(2) {\n            width: 30%;\n        }\n\n        &.sms .column:nth-child(2) {\n            width: 50%;\n        }\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_header-footer.less",
    "content": "/*=========================================\n3. Header & Footer\n=========================================== */\n\n/* -- If you want an unique, global header/footer,read this:\nhttps://github.com/webslides/webslides/issues/57 -- */\n\nheader,\nfooter,\n#navigation {\n    padding: 2.4rem;\n    transition: all 0.4s ease-in-out;\n    width: 100%;\n}\n\nheader p,\nfooter p {\n    line-height: 4.8rem;\n    margin-bottom: 0;\n}\n\nheader[role='banner'] img,\nfooter img {\n    height: 4rem;\n    vertical-align: middle;\n}\n\nfooter {\n    position: relative;\n}\n\nheader,\nfooter {\n    z-index: 3;\n}\n\nheader,\n.ws-ready footer {\n    left: 0;\n    position: absolute;\n    top: 0;\n}\n\n.ws-ready footer {\n    bottom: 0;\n    top: auto;\n}\n\n// Remove \"opacity=0\" if you want an unique, visible header on each slide\nheader[role='banner'] {\n    opacity: 0;\n\n    &:hover {\n        opacity: 1;\n    }\n}\n\n@media (max-width: 767px) {\n    footer .alignleft,\n    footer .alignright {\n        display: block;\n        float: none;\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_logo.less",
    "content": "/*=== 3.1. Logo === */\n\n.logo {\n    text-transform: lowercase;\n\n    a {\n        background: url('../../images/logos/logo.svg') no-repeat 0 0;\n        background-size: 4.8rem;\n        float: left;\n        height: 4.8rem;\n        text-indent: -4000px;\n        /*If you remove text-indent and add: */\n        /*padding-left: 6rem;*/\n        vertical-align: middle;\n        width: 4.8rem;\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_longform.less",
    "content": "/*=========================================\n15. Longform\n=========================================== */\n/* -- Posts = .wrap.longform -- */\n\n.longform {\n    width: 72rem;\n    /* Why 72rem=720px?\n  90-95 characters per line = better reading speed */\n\n    & .alignleft,\n    & .alignright {\n        max-width: 40%;\n    }\n\n    img.aligncenter,\n    figure.aligncenter {\n        margin-bottom: 3.2rem;\n        margin-top: 3.2rem;\n    }\n\n    ul,\n    ol {\n        margin-bottom: 3.2rem;\n    }\n\n    ul ol,\n    ol ul,\n    ul ul,\n    ol ol {\n        margin-bottom: 0;\n    }\n\n    figcaption p,\n    [class*='text-pull-'] p {\n        font-size: 1.6rem;\n        line-height: 2.4rem;\n    }\n\n    /* Mobile: video full width */\n    .text-pull.embed {\n        margin-left: -2.4rem;\n        margin-right: -2.4rem;\n        padding-bottom: 60.6%;\n    }\n\n    @media (min-width: 1280px) {\n        [class*='text-pull-'] {\n            max-width: 32%;\n        }\n\n        .text-pull-right {\n            margin-right: -256px;\n        }\n\n        .text-pull-left {\n            margin-left: -256px;\n        }\n    }\n\n    @media (min-width: 1024px) {\n        .text-quote {\n            margin-left: -4.8rem;\n            margin-right: -4.8rem;\n        }\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_media.less",
    "content": "/*=== 1.3 Responsive Media (videos, iframe...) === */\n\n.embed {\n    height: 0;\n    overflow: hidden;\n    /*aspect ratio:16:9*/\n    padding-bottom: 56.6%;\n    /*aspect ratio: 4:3*/\n    /*padding-bottom: 75%;*/\n    position: relative;\n\n    iframe,\n    object,\n    embed,\n    .echarts,\n    video {\n        height: 100%;\n        left: 0;\n        margin: 0;\n        position: absolute;\n        top: 0;\n        width: 100%;\n    }\n\n    /* -- Responsive background video\n  https://fvsch.com/code/video-background/ -- */\n\n    .fullscreen > & {\n        bottom: 0;\n        height: auto;\n        left: 0;\n        padding-bottom: 0;\n        position: fixed;\n        right: 0;\n        top: 0;\n\n        /* 1. No object-fit support: */\n        & > iframe,\n        & > object,\n        & > .echarts,\n        & > .lang-mermaid,\n        & > embed,\n        & > video {\n            @media (min-aspect-ratio: 16 / 9) {\n                height: 300%;\n                top: -100%;\n            }\n\n            @media (max-aspect-ratio: 16 / 9) {\n                left: -100%;\n                width: 300%;\n            }\n\n            /* 2. If supporting object-fit, overriding (1): */\n            @supports (object-fit: cover) {\n                height: 100%;\n                left: 0;\n                object-fit: cover;\n                top: 0;\n                width: 100%;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_navigation.less",
    "content": "/*=========================================\n4. Navigation\n=========================================== */\n\n/*=== 4.1. Navbars === */\n\nnav ul {\n    display: flex;\n    flex-wrap: wrap;\n    /*====align left====*/\n    justify-content: flex-start;\n    /* ==== align center ====*/\n    /*justify-content: center; */\n    /*====align right====*/\n    /* justify-content: flex-end; */\n    /*====separated columns li a====*/\n    /* justify-content: space-between; */\n    /*====separated columns centered li a====*/\n    /*justify-content: space-around;*/\n\n    li {\n        float: left;\n        list-style: none;\n        position: relative;\n    }\n}\n\nnav ul li:first-child,\nnav[role='navigation'] ul li {\n    margin-left: 0;\n}\n\nnav[role='navigation'] li a {\n    display: flex;\n    justify-content: center;\n    line-height: 4.8rem;\n    max-width: 100%;\n    padding: 0 1.6rem;\n    position: relative;\n    text-decoration: none;\n\n    svg {\n        margin: 1.5rem 0.4rem 1.5rem 0;\n    }\n}\n\nheader nav ul {\n    justify-content: flex-end;\n    margin: 0;\n}\n\nnav.aligncenter ul,\n.aligncenter nav ul {\n    /* ==== align center ====*/\n    justify-content: center;\n}\n\nnav.navbar ul li {\n    /*====full float li a ====*/\n    flex: 1 1 auto;\n}\n\n@media (max-width: 568px) {\n    nav.navbar ul {\n        flex-flow: column wrap;\n        padding: 0;\n    }\n\n    nav.navbar li a {\n        justify-content: flex-start;\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_print.less",
    "content": "/*=========================================\n17. PRINT\n=========================================== */\n\n// sass-lint:disable no-important\n@media print {\n    @page {\n        margin: 0.5cm;\n        size: A4 landscape;\n    }\n\n    // Black prints faster\n    * {\n        // background: transparent !important;\n        // color: @black !important;\n        // filter: none !important;\n        // text-shadow: none !important;\n    }\n\n    html,\n    body,\n    #webslides {\n        height: auto !important;\n        overflow: auto !important;\n        width: auto !important;\n    }\n\n    #webslides {\n        overflow-x: auto !important;\n        overflow-y: auto !important;\n    }\n\n    section,\n    .slide {\n        display: flex !important;\n        height: auto !important;\n    }\n\n    section * {\n        animation: none;\n    }\n\n    table,\n    figure {\n        page-break-inside: avoid;\n    }\n\n    #counter,\n    #navigation {\n        display: none;\n    }\n\n    .build .tobuild {\n        opacity: 1 !important;\n        visibility: visible !important;\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_promos.less",
    "content": "/*=============================================\n7. Promos/Offers (pricing, tagline, CTA...)\n=============================================== */\n\n.cta {\n    display: flex;\n    flex-wrap: wrap;\n    justify-content: center;\n    .number,\n    .benefit {\n        display: flex;\n        flex-direction: column;\n        justify-content: center;\n        max-width: 100%;\n        padding: 0.8rem;\n    }\n    .number {\n        text-align: center;\n    }\n    .benefit {\n        max-width: 100%;\n        text-align: center;\n    }\n    .number span {\n        display: block;\n        font-size: 8rem;\n        line-height: 8rem;\n    }\n    .number span sup {\n        font-size: 4rem;\n    }\n    p {\n        margin-bottom: 0;\n    }\n}\n\n@media (min-width: 768px) {\n    .cta .number,\n    .cta .benefit {\n        max-width: 50%;\n        padding: 4.8rem;\n    }\n    .cta .benefit {\n        text-align: left;\n    }\n    .cta .number span {\n        font-size: 16rem;\n        line-height: 16rem;\n        sup {\n            font-size: 6rem;\n            vertical-align: middle;\n        }\n    }\n}\n\n/* --- Header CTA --- */\n\n.cta-cover {\n    display: table;\n    width: 100%;\n    h1 strong {\n        font-weight: 400;\n    }\n    @media (min-width: 1024px) {\n        h1 {\n            float: left;\n            max-width: 80%;\n        }\n        h1 strong {\n            display: block;\n        }\n        .button {\n            margin-top: 1.2rem;\n        }\n        .try {\n            text-align: center;\n        }\n    }\n    @media (max-width: 1023px) {\n        .alignright {\n            float: none;\n        }\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_quotes.less",
    "content": "/*=========================================\n11. Quotes\n=========================================== */\n\nblockquote {\n    display: inline-block;\n    position: relative;\n    p {\n        font-size: 2.4rem;\n        line-height: 4rem;\n        &:last-child {\n            margin-bottom: 3.2rem;\n        }\n    }\n}\n\n/* -- Interviews dl.text-interview -- */\n\ndd blockquote p:last-child {\n    margin-bottom: 0;\n}\n\ncite {\n    display: block;\n    text-align: center;\n    &:before {\n        content: '\\2014 \\2009';\n        margin-right: 6px;\n    }\n}\n\ncite span {\n    display: block;\n}\n\n/* -- A big Blockquote -- */\n\n/* .wall will be deprecated soon. Use .text-quote ;) */\n\n.text-quote,\n.wall {\n    /* Versatility: blockquote, p, h2... */\n    position: relative;\n    &:before {\n        content: '\\201C';\n        font-family: arial, sans-serif;\n        font-size: 12rem;\n        height: 5.6rem;\n        left: -0.8rem;\n        line-height: 1;\n        position: absolute;\n        text-align: center;\n        top: -4rem;\n        width: 5.6rem;\n    }\n    @media (min-width: 768px) {\n        padding-left: 6.4rem;\n        p {\n            font-size: 3.2rem;\n            line-height: 4.8rem;\n        }\n        &:before {\n            left: 0.8rem;\n            top: -1.6rem;\n        }\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_slides-bg.less",
    "content": "/*=== 5.3 Slides - Background Images/Videos === */\n\n.background,\n[class*='background-'] {\n    background-repeat: no-repeat;\n    bottom: 0;\n    left: 0;\n    position: absolute;\n    right: 0;\n    top: 0;\n}\n\n/*=== BG Positions === */\n\n.background {\n    background-position: center;\n    background-size: cover;\n\n    &-top {\n        background-position: top;\n        background-size: cover;\n    }\n\n    &-bottom {\n        background-position: bottom;\n        background-size: cover;\n    }\n\n    &-center {\n        background-position: center;\n    }\n\n    &-center-top {\n        background-position: center top;\n    }\n\n    &-right-top {\n        background-position: right top;\n    }\n\n    &-left-top {\n        background-position: left top;\n    }\n\n    &-center-bottom,\n    &-left-bottom,\n    &-right-bottom,\n    &-left,\n    &-right {\n        background-position: center bottom;\n    }\n\n    @media (min-width: 1024px) {\n        &-left-bottom {\n            background-position: left bottom;\n        }\n\n        &-right-bottom {\n            background-position: right bottom;\n        }\n\n        &-right {\n            background-position: right;\n        }\n\n        &-left {\n            background-position: left;\n        }\n    }\n\n    /*fullscreen video\n    <video class=\"background-video\">\n  */\n\n    &-video {\n        height: 100%;\n        object-fit: fill;\n        width: 100%;\n    }\n}\n/*=== bg image/video overlay === */\n/*-- [class*=\"bg-\"] .background.dark, [class*=\"bg-\"] .embed.dark...  -- */\n\n[class*='bg-'] .light,\n[class*='bg-'] .light {\n    opacity: 0.8;\n}\n\n[class*='bg-'] .dark,\n[class*='bg-'] .dark {\n    opacity: 0.2;\n}\n\n[class*='bg-'] .background-video.dark {\n    opacity: 0.5;\n}\n\n@media (max-width: 1023px) {\n    [class*='background-'] {\n        animation: fadeIn ease-in 0.2;\n        opacity: 0.2;\n    }\n\n    .background-video {\n        opacity: 0.8;\n    }\n}\n\n/*=== Animated Background Image === */\n\n.background.anim {\n    animation: anim 80s linear infinite;\n    background-position: center top;\n    background-repeat: repeat;\n    background-size: 100%;\n    height: 200%;\n}\n\n/*=== Background with a frame === */\n/*<span class=\"background\" style=\"background-image:url('image.jpg')\"></span>\n<span class=\"background frame\"></span>*/\n\n[class*='background'].frame {\n    margin: 2.4rem;\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_slides-navigation.less",
    "content": "/* === 5.2 Counter / Navigation Slides  === */\n\n#navigation {\n    animation: fadeIn 8s;\n    bottom: 0;\n    left: 0;\n    margin-left: auto;\n    margin-right: auto;\n    opacity: 0;\n    position: fixed;\n    right: 0;\n    width: 24.4rem;\n    /* hover/visibility */\n    z-index: 4;\n\n    &:hover {\n        opacity: 1;\n    }\n\n    p {\n        margin-bottom: 0;\n    }\n}\n\n#counter {\n    display: block;\n    line-height: 4.8rem;\n    margin-left: auto;\n    margin-right: auto;\n    position: relative;\n    text-align: center;\n    width: 10rem;\n\n    a:hover {\n        padding: 0.8rem;\n    }\n}\n\na#next,\na#previous {\n    border-radius: 0.4rem;\n    cursor: pointer;\n    font-size: 2.4rem;\n    height: 4rem;\n    padding: 0.8rem;\n    position: absolute;\n    text-align: center;\n    width: 4rem;\n}\n\na#next {\n    right: 3.2rem;\n}\n\na#previous {\n    left: 3.2rem;\n}\n\n@media (max-width: 1024px) {\n    #navigation {\n        animation: fadeIn 6s;\n        background: url('../../images/swipe.svg') no-repeat center top;\n        background-size: 4.8rem;\n    }\n\n    #navigation a,\n    #counter {\n        display: none;\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_slides.less",
    "content": "/*============================================\n5. SLIDES (Full Screen)\nVertically and horizontally centered\n============================================== */\n\n/* Fade transition to all slides.\n* = All HTML elements will have those styles.*/\n\nsection * {\n    animation: fadeIn 0.6s ease-in-out;\n}\n\nsection .background,\nsection .light,\nsection .dark {\n    animation-duration: 0s;\n}\n\n/*=== Section = Slide === */\n\nsection,\n.slide {\n    display: flex;\n    flex-direction: column;\n    justify-content: center;\n    min-height: 100vh;\n    /*Fullscreen*/\n    /* Prototyping? min-height: 720px (Baseline: 8px = .8rem)*/\n    padding: 2.4rem;\n    /*Fixed/Visible header? padding-top: 12rem; */\n    page-break-after: always;\n    position: relative;\n    word-wrap: break-word;\n    @media (min-width: 1024px) {\n        padding-bottom: 12rem;\n        padding-top: 12rem;\n    }\n}\n\n/*slide with no padding (full card, .embed youtube video...) */\n\n.fullscreen {\n    padding: 0;\n    /* Fixed/Visible header?\n  padding:8.2rem 0 0 0;\n  */\n    .wrap {\n        @media (min-width: 1024px) {\n            width: 100%;\n        }\n    }\n}\n\n/* slide alignment - top */\n\n.slide-top {\n    justify-content: flex-start;\n}\n\n/* slide alignment - bottom */\n\n.slide-bottom {\n    justify-content: flex-end;\n}\n\n/*== 5.1. Mini container width:50%\nAligned items [class*=\"content-\"]=== */\n\n[class*='content-'] {\n    position: relative;\n    text-align: left;\n}\n\n.wrap[class*='bg-'],\n.wrap.frame,\n[class*='content-'][class*='bg-'],\n[class*='content-'].frame,\n[class*='align'][class*='bg-'] {\n    padding: 4.8rem;\n}\n\nform[class*='bg-'] {\n    padding: 2.4rem;\n}\n\n[class*='content-'] > [class*='content-'] p {\n    font-size: 1.8rem;\n    line-height: 3.2rem;\n}\n\n.content-center {\n    margin: 0 auto;\n    text-align: center;\n}\n\n@media (min-width: 768px) {\n    [class*='content-'] {\n        width: 50%;\n        &:after,\n        &:before {\n            content: '';\n            display: table;\n        }\n        &:after {\n            clear: both;\n        }\n    }\n    .content-left {\n        float: left;\n    }\n    .content-right {\n        float: right;\n    }\n    [class*='content-'] + [class*='content-'] {\n        margin-bottom: 4.8rem;\n        padding-left: 2.4rem;\n    }\n    [class*='content-'] + [class*='size-'] {\n        clear: both;\n        margin-top: 6.4rem;\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_speaker-note.less",
    "content": "section .speaker-note {\n    animation: none;\n    display: none;\n    position: absolute;\n    width: 100%;\n    height: 100%;\n    top: 0;\n    left: 0;\n    padding: 1em;\n    background: rgba(0, 0, 0, 0.3);\n    opacity: 1;\n    z-index: 99999;\n    flex-flow: row;\n    justify-content: center;\n    box-sizing: border-box;\n    transition: all 0.4s ease-in-out;\n    > .wrap {\n        padding: 2em;\n        text-align: left; // .display-flex;\n        background: #fff;\n    }\n    &.show {\n        display: flex;\n        animation-duration: 0.8s;\n        animation-fill-mode: both;\n        animation-name: slideInUp;\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_tables.less",
    "content": "/*=========================================\n13. Tables\n=========================================== */\n\ntable {\n    margin-bottom: 3.2rem;\n    margin-top: 3.2rem;\n}\n\ntd,\nth,\nthead {\n    border-spacing: 0;\n    padding: 0.7rem 2.4rem;\n}\n\ntable.text-hight {\n    td,\n    th,\n    thead {\n        border-spacing: 0;\n        padding: 1.7rem 2.4rem;\n    }\n}\n\nthead th,\nth {\n    cursor: default;\n    font-weight: 600;\n    text-align: left;\n    text-transform: uppercase;\n    white-space: nowrap;\n}\n\nthead,\ntd.goals {\n    font-weight: 600;\n    text-shadow: none;\n}\n\ntr > td {\n    font-weight: 400;\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_toc.less",
    "content": "/*===========================================\n9. Table of contents\n============================================= */\n\n.toc,\n.toc ol > li:before,\n.chapter {\n    position: relative;\n    z-index: 2;\n}\n\n.toc {\n    ol {\n        counter-reset: item;\n        position: relative;\n\n        & > li:before {\n            content: counters(item, '.') '. ';\n            display: table-cell;\n            padding-right: 0.8rem;\n            width: 2.4rem;\n        }\n\n        li li:before {\n            content: counters(item, '.') ' ';\n        }\n    }\n\n    li {\n        counter-increment: item;\n        display: table;\n        font-weight: 400;\n        margin-bottom: 0.8rem;\n        margin-left: 0;\n        transition: 0.3s;\n        width: 100%;\n\n        li {\n            font-weight: 300;\n            margin-bottom: 0;\n            margin-left: 0;\n        }\n\n        .toc-page:before {\n            content: '';\n            display: block;\n            left: 0;\n            margin-top: 1.8rem;\n            position: absolute;\n            right: 4rem;\n        }\n\n        & > a {\n            display: inline-block;\n            width: 100%;\n        }\n\n        a:hover span {\n            font-weight: 600;\n        }\n\n        a:hover .toc-page:before {\n            border-bottom-width: 2px;\n        }\n    }\n}\n\n.chapter {\n    display: inline-block;\n    font-size: 1.8rem;\n    line-height: 3.2rem;\n    padding-right: 0.8rem;\n}\n\n.toc-page {\n    float: right;\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_with-note.less",
    "content": ".with-note {\n    background: rgba(0, 0, 0, 0.3);\n    .slide.current,\n    .slide {\n        &:not([class*='bg-']) {\n            background: #f7f9fb;\n        }\n        width: 100vw !important;\n        overflow: visible;\n        transition: none !important;\n        transform-origin: 0 0 !important;\n        transform: scale(0.6) translate3d(0.5vw, 0.5vw, 0);\n        .speaker-note {\n            display: flex;\n            width: 100vw;\n            height: 38vh;\n            padding: 0;\n            background: transparent;\n            transform: translate3d(35vw, 115vh, 0) scale(1.5);\n            transition: none;\n            > .wrap {\n                padding: 1em;\n                overflow-y: auto;\n            }\n            &.hide,\n            &.show {\n                animation: none;\n            }\n        }\n    }\n    .slide.current + .slide {\n        position: absolute;\n        top: 0;\n        left: 0;\n        transform: translate3d(64vw, 13vh, 0) scale(0.35);\n        opacity: 1 !important;\n        display: flex !important;\n        .speaker-note {\n            display: none !important;\n        }\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_work.less",
    "content": "/*=========================================\n8. Work/Resumé/CV <ul class=\"work\">\n=========================================== */\n.work {\n    clear: both;\n    display: flex;\n    flex-direction: column;\n    text-align: left;\n\n    h1 + &,\n    h2 + &,\n    h3 + &,\n    p + & {\n        margin-top: 4.8rem;\n    }\n\n    li {\n        flex: 1;\n        list-style: none;\n        margin: 0;\n        position: relative;\n    }\n\n    p {\n        margin-bottom: 0;\n        transition: 0.3s;\n    }\n\n    li a {\n        display: block;\n        float: left;\n        height: 100%;\n        padding: 2.4rem 0;\n        width: 100%;\n    }\n\n    li p {\n        padding-left: 1.2rem;\n    }\n\n    li.work-label p {\n        padding-left: 0;\n    }\n\n    li a:hover p:first-child {\n        padding-left: 1.6rem;\n    }\n\n    li p:last-child {\n        position: absolute;\n        right: 1.2rem;\n        top: 2.4rem;\n    }\n\n    li.work-label p:last-child {\n        right: 0;\n        top: 0;\n    }\n\n    &-label {\n        float: left;\n        font-weight: 600;\n        padding: 0 0 2.4rem;\n        width: 100%;\n    }\n\n    &-title {\n        display: block;\n        padding-right: 1.2rem;\n        width: 75%;\n    }\n}\n\n@media (min-width: 768px) {\n    .work-label p,\n    .work li p {\n        float: left;\n        margin-right: 2%;\n        width: 25%;\n    }\n\n    .work li.work-label p:last-child,\n    .work li p:last-child {\n        float: right;\n        margin-right: 0;\n        padding-right: 1.2rem;\n        position: relative;\n        right: auto;\n        text-align: right;\n        top: auto;\n    }\n\n    .work li p.work-date {\n        width: 120px;\n    }\n}\n\n@media (max-width: 768px) {\n    .work-client,\n    .work-label .work-services {\n        clip: rect(1px, 1px, 1px, 1px);\n        height: 1px;\n        overflow: hidden;\n        position: absolute;\n        width: 1px;\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/modules/_zoom.less",
    "content": "/*==============================================\n18. Slides Index: Thumbnails navigation gallery\n================================================ */\n\n#webslides-zoomed {\n    align-content: flex-start;\n    align-items: flex-start;\n    flex-direction: row;\n    justify-content: flex-start;\n    min-height: 100vh;\n    position: relative;\n    z-index: 2;\n\n    &.disabled {\n        left: -100000px;\n        position: absolute;\n    }\n\n    .slide {\n        height: 400%;\n        width: 400%;\n\n        @media screen and (orientation: portrait), screen and (max-width: 768px) and (orientation: landscape) {\n            height: 200%;\n            width: 200%;\n        }\n\n        @media (max-aspect-ratio: 2 / 3) {\n            height: 200%;\n            width: 200%;\n        }\n    }\n\n    > .wrap {\n        @media (min-width: 1024px) {\n            padding-bottom: 12rem;\n            padding-top: 12rem;\n        }\n    }\n\n    > .wrap > .grid > .column {\n        align-self: auto;\n        flex: 0 1 auto;\n        order: 0;\n        position: relative;\n        width: 25%;\n\n        @media screen and (max-width: 567px) {\n            width: 100%;\n        }\n\n        @media screen and (min-width: 568px) and (max-width: 1024px) {\n            width: 50%;\n        }\n\n        @media screen and (max-width: 567px) and (orientation: portrait) {\n            width: 100%;\n        }\n\n        > .wrap-zoom {\n            border-radius: 0.3rem;\n            display: inline-block;\n            height: 25vh;\n            overflow: hidden;\n            position: relative;\n            transition: 0.3s;\n\n            @media screen and (max-width: 567px) {\n                height: 50vh;\n            }\n\n            @media screen and (min-width: 568px) and (max-width: 1023px) {\n                height: 33vh;\n            }\n\n            @media screen and (orientation: portrait) {\n                height: 50vw;\n            }\n\n            &:hover {\n                transform: scale(1.02);\n                z-index: 2;\n            }\n\n            &.current {\n                transform: scale(1.08);\n            }\n        }\n\n        > .wrap-zoom > .zoom-layer {\n            background: transparent;\n            cursor: pointer;\n            height: 100%;\n            position: absolute;\n            width: 100%;\n        }\n    }\n\n    .column > .wrap-zoom > .slide {\n        clip: rect(0 auto auto 0);\n        display: flex !important; //  sass-lint:disable-line no-important\n        left: 0;\n        position: absolute;\n        top: 0;\n        transform: scale(0.25) translate(-150%, -150vh);\n\n        @media screen and (orientation: portrait), screen and (max-width: 768px) and (orientation: landscape) {\n            transform: scale(0.5) translate(-50%, -50%);\n        }\n\n        @media (max-aspect-ratio: 2 / 3) {\n            transform: scale(0.5) translate(-50%, -50%);\n        }\n    }\n\n    & .column {\n        opacity: 0;\n        transform: scale(1.2);\n        transition: opacity 0.4s, transform 0.4s;\n        transition-delay: 0.2s;\n    }\n\n    &.in {\n        .column {\n            opacity: 1;\n            transform: scale(1);\n        }\n    }\n}\n\n.text-slide-number {\n    display: inline-block;\n    margin: 0.8rem auto;\n    text-align: center;\n}\n\n#webslides {\n    transition: filter 0.3s;\n\n    &.disabled,\n    &.zooming {\n        position: fixed;\n        width: 100%;\n        z-index: 0;\n    }\n\n    &.disabled {\n        /*\n    filter: blur(10px);\n    transform: scale(1.1);\n    */\n        /* Blur makes scroll no accesible */\n        width: calc(~'100% - 10px');\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/utils/_animations.less",
    "content": "/* === 1.2 Animations ================\nJust 5 basic animations:\n.fadeIn, .fadeInUp, .zoomIn, .slideInLeft, and .slideInRight\nhttps://github.com/daneden/animate.css */\n\n/*-- fadeIn -- */\n\n@keyframes fadeIn {\n    from {\n        opacity: 0;\n    }\n\n    to {\n        opacity: 1;\n    }\n}\n\n.fadeIn {\n    animation: fadeIn 1s;\n}\n\n/*-- fadeInUp -- */\n@keyframes fadeInUp {\n    from {\n        opacity: 0;\n        transform: translate3d(0, 100%, 0);\n    }\n\n    to {\n        opacity: 1;\n        transform: none;\n    }\n}\n\n.fadeInUp {\n    animation: fadeInUp 1s;\n}\n\n/*-- zoomIn -- */\n@keyframes zoomIn {\n    from {\n        transform: scale3d(0.3, 0.3, 0.3);\n    }\n\n    50% {\n        opacity: 1;\n    }\n}\n\n.zoomIn {\n    animation: zoomIn 1s;\n}\n\n/*-- slideInLeft -- */\n@keyframes slideInLeft {\n    from {\n        transform: translate3d(-100%, 0, 0);\n        visibility: visible;\n    }\n\n    to {\n        transform: translate3d(0, 0, 0);\n    }\n}\n\n.slideInLeft {\n    animation: slideInLeft 1s;\n    animation-fill-mode: both;\n}\n\n/*-- slideInRight -- */\n@keyframes slideInRight {\n    from {\n        transform: translate3d(100%, 0, 0);\n        visibility: visible;\n    }\n\n    to {\n        transform: translate3d(0, 0, 0);\n    }\n}\n\n.slideInRight {\n    animation: slideInRight 1s;\n    animation-fill-mode: both;\n}\n\n/* Animated Background (Matrix) */\n@keyframes anim {\n    0% {\n        transform: translateY(0);\n    }\n\n    100% {\n        transform: translateY(-1200px);\n    }\n}\n\n/* Duration */\n.slow {\n    animation-duration: 4s;\n\n    & + & {\n        animation-duration: 5s;\n    }\n}\n\n// slideOutScaleLeft\n@keyframes slideOutScaleLeft {\n    from {\n        opacity: 1;\n    }\n    to {\n        transform: translateX(-100%) scale(0.9);\n        opacity: 0;\n    }\n}\n\n@keyframes slideInFromRight {\n    from {\n        transform: translateX(100%);\n    }\n    to {\n        transform: translateX(0);\n    }\n}\n\n@keyframes slideInFromLeft {\n    from {\n        transform: translateX(-100%);\n    }\n    to {\n        transform: translateX(0);\n    }\n}\n\n@keyframes slideOutScaleRight {\n    from {\n        opacity: 1;\n    }\n    to {\n        transform: translateX(100%) scale(0.9);\n        opacity: 0;\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/utils/_bugs.less",
    "content": "/*=========================================\n16. SAFARI BUGS (flex-wrap)\nSolution: stackoverflow.com/questions/34250282/flexbox-safari-bug-flex-wrap\n=========================================== */\n\n.flexblock:before,\n.flexblock:after,\n.grid:before,\n.grid:after,\n.cta:before,\n.cta:after {\n    width: 0;\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/utils/_clear.less",
    "content": "/*=== Clearing === */\n\nheader,\nmain,\nsection,\naside,\nfooter,\n.clear,\n.wrap {\n    &:before,\n    &:after {\n        content: '';\n        display: table;\n    }\n\n    &:after {\n        clear: both;\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/assets/less/utils/_reset.less",
    "content": "// sass-lint:disable no-vendor-prefixes\n/*\n=========================================\n0. CSS Reset & Normalize\n=========================================\n*/\nhtml,\nbody,\ndiv,\nspan,\napplet,\nobject,\niframe,\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\np,\nblockquote,\npre,\na,\nabbr,\nacronym,\naddress,\nbig,\ncite,\ncode,\ndel,\ndfn,\nem,\nimg,\nins,\nkbd,\nq,\ns,\nsamp,\nsmall,\nstrike,\nstrong,\nsub,\nsup,\ntt,\nvar,\nb,\nu,\ni,\ncenter,\ndl,\ndt,\ndd,\nol,\nul,\nli,\nfieldset,\nform,\nlabel,\nlegend,\ntable,\ncaption,\ntbody,\ntfoot,\nthead,\ntr,\nth,\ntd,\narticle,\naside,\ncanvas,\ndetails,\nembed,\nfigure,\nfigcaption,\nfooter,\nheader,\nmenu,\nnav,\noutput,\nruby,\nsection,\nsummary,\ntime,\nmark,\naudio,\nvideo {\n    border: 0;\n    font: inherit;\n    font-size: 100%;\n    margin: 0;\n    padding: 0;\n    vertical-align: baseline;\n}\n\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n    display: block;\n}\n\nbody {\n    line-height: 1;\n}\n\nblockquote,\nq {\n    quotes: '' '';\n}\n\nblockquote:before,\nblockquote:after,\nq:before,\nq:after {\n    content: '';\n}\n\ntable {\n    border-collapse: collapse;\n    border-spacing: 0;\n    margin-bottom: 24px;\n    width: 100%;\n}\n\nhtml {\n    box-sizing: border-box;\n}\n\n*,\n*::before,\n*::after {\n    box-sizing: inherit;\n}\n\naudio,\ncanvas,\nprogress,\nvideo {\n    display: inline-block;\n    vertical-align: baseline;\n}\n\nembed,\niframe,\nobject {\n    max-width: 100%;\n}\n\naudio:not([controls]) {\n    display: none;\n    height: 0;\n}\n\n[hidden],\ntemplate {\n    display: none;\n}\n\nul {\n    list-style: square;\n    text-indent: inherit;\n}\n\nol {\n    list-style: decimal;\n}\n\nb,\nstrong {\n    font-weight: 600;\n}\n\na {\n    background-color: transparent;\n}\n\na:active,\na:hover {\n    outline: 0;\n}\n\nsup,\nsub {\n    font-size: 0.75em;\n    height: 0;\n    line-height: 2.2em;\n    position: relative;\n    vertical-align: baseline;\n}\n\nsup {\n    bottom: 1ex;\n}\n\nsub {\n    top: 0.5ex;\n}\n\nsmall {\n    font-size: 0.75em;\n    line-height: 1.72;\n}\n\nbig {\n    font-size: 1.25em;\n}\n\nhr {\n    border: 0;\n    clear: both;\n    display: block;\n    height: 1px;\n    margin: 3.2rem auto;\n    text-align: center;\n    width: 100%;\n}\n\nh2 + hr,\nh3 + hr {\n    margin-bottom: 4.8rem;\n}\n\np + hr {\n    margin-bottom: 4rem;\n}\n\ndfn,\ncite,\nem,\ni {\n    font-style: italic;\n}\n\nabbr,\nacronym {\n    cursor: help;\n}\n\nmark,\nins {\n    padding: 0 4px;\n    text-decoration: none;\n    text-shadow: none;\n}\n\n::-moz-selection {\n    text-shadow: none;\n}\n\n::selection {\n    text-shadow: none;\n}\n\nimg {\n    border: 0;\n    height: auto;\n    max-width: 100%;\n}\n\nimg:hover {\n    opacity: 0.9;\n}\n\nsvg:not(:root) {\n    overflow: hidden;\n}\n\nfigure {\n    line-height: 0;\n    margin: 0;\n    position: relative;\n}\n\noptgroup {\n    font-weight: bold;\n}\n\ntd,\nth {\n    padding: 0;\n}\n\ndt {\n    font-weight: bold;\n}\n\ndd {\n    margin: 0;\n}\n"
  },
  {
    "path": "packages/nodeppt-js/index.js",
    "content": "import WebSlides from 'webslides/src/js/modules/webslides';\nimport './assets/less/full.less';\nimport './assets/less/index.less';\nimport 'animate.css';\n// import ItemBuild from './lib/item-build';\nimport Keyboard from './plugins/keyboard';\nimport SpeakerMode from './plugins/speaker-mode';\nimport SpeakerNote from './plugins/speaker-note';\nimport Echarts from './plugins/echarts';\nimport Mermaid from './plugins/mermaid';\n\n\nWebSlides.registerPlugin('echarts', Echarts);\nWebSlides.registerPlugin('mermaid', Mermaid);\nWebSlides.registerPlugin('keyboard', Keyboard);\nWebSlides.registerPlugin('speakermode', SpeakerMode);\nWebSlides.registerPlugin('speakernote', SpeakerNote);\nexport default WebSlides;\n"
  },
  {
    "path": "packages/nodeppt-js/package.json",
    "content": "{\n    \"name\": \"nodeppt-js\",\n    \"version\": \"2.2.1\",\n    \"description\": \"\",\n    \"main\": \"index.js\",\n    \"scripts\": {\n        \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n    },\n    \"author\": {\n        \"name\": \"Theo Wang\",\n        \"email\": \"ksky521@gmail.com\"\n    },\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"git://github.com/ksky521/nodeppt\"\n    },\n    \"license\": \"MIT\",\n    \"keywords\": [\n        \"presentation\",\n        \"powerpoint\",\n        \"slideshow\",\n        \"keynote\",\n        \"ppt\",\n        \"slide\",\n        \"revealjs\",\n        \"impressjs\",\n        \"markdown-it\",\n        \"posthtml\",\n        \"webpack\",\n        \"nodeppt\",\n        \"markdown\"\n    ],\n    \"dependencies\": {\n        \"animate.css\": \"^3.7.0\",\n        \"nodeppt-template-default\": \"^1.0.1\",\n        \"username\": \"^5.1.0\",\n        \"webslides\": \"1.5.0\"\n    },\n    \"gitHead\": \"9fba34ba1a8cc3ab149c2447c313e25b1e25093e\"\n}\n"
  },
  {
    "path": "packages/nodeppt-js/plugins/echarts.js",
    "content": "import DOM from 'webslides/src/js/utils/dom';\nimport {default as Slide, Events as SlideEvents} from 'webslides/src/js/modules/slide';\n/* global echarts */\nexport default class Echarts {\n    constructor(wsInstance) {\n        this.ws_ = wsInstance;\n\n        const echartsNode = DOM.toArray(this.ws_.el.querySelectorAll('.echarts'));\n        const echartsData = DOM.toArray(this.ws_.el.querySelectorAll('.echarts-data'));\n\n        if (echartsNode.length) {\n            echartsNode.forEach((chart, j) => {\n                const {i} = Slide.getSectionFromEl(chart);\n                const slide = wsInstance.slides[i - 1];\n                slide.echartsInit = false;\n                slide.echartsNode = chart;\n                slide.echartsData = echartsData[j];\n                // slide.el.addEventListener(SlideEvents.ENTER, Echarts.onSectionEnter);\n                slide.el.addEventListener(SlideEvents.ENABLE, Echarts.onSectionEnter);\n            });\n        }\n    }\n\n    static onSectionEnter(event) {\n        const slide = event.detail.slide || {};\n        const {echartsNode, echartsInit, echartsData} = slide;\n        if (!echartsInit) {\n            setTimeout(() => {\n                const theme =\n                    window.pluginsOptions && window.pluginsOptions.echarts\n                        ? window.pluginsOptions.echarts.theme\n                        : undefined;\n                const et = echarts.init(echartsNode, theme);\n                try {\n                    const data = JSON.parse(echartsData.innerHTML.trim());\n                    et.setOption(data);\n                    slide.echartsInit = true;\n                } catch (e) {}\n            }, 800);\n        }\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/plugins/keyboard.js",
    "content": "import Keys from 'webslides/src/js//utils/keys';\nimport DOM from 'webslides/src/js/utils/dom';\n\n/**\n * Keyboard interaction plugin.\n * 修改原生的 keyboard，支持单条 build\n */\nexport default class Keyboard {\n    /**\n     * @param {WebSlides} wsInstance The WebSlides instance\n     * @constructor\n     */\n    constructor(wsInstance) {\n        /**\n         * @type {WebSlides}\n         * @private\n         */\n        this.ws_ = wsInstance;\n        this.enable_ = false;\n        this.init_();\n        this.bindEvent_();\n    }\n    bindEvent_() {\n        const el = this.ws_.el;\n        el.addEventListener('ws:slide-change', this.slideBuild_.bind(this), false);\n\n        document.addEventListener('keydown', this.onKeyPress_.bind(this), false);\n        // 接受远程控制事件\n        document.addEventListener('control:keydown', this.onKeyPress_.bind(this), false);\n    }\n    init_() {\n        const toBuildNode = toArray(this.ws_.el.querySelectorAll('.build>*'));\n        if (toBuildNode.length) {\n            toBuildNode.forEach(node => {\n                node.classList.add('tobuild');\n            });\n        }\n    }\n    /**\n     * Reacts to the keydown event. It reacts to the arrows and space key\n     * depending on the layout of the page.\n     * @param {KeyboardEvent} event The key event.\n     * @private\n     */\n    onKeyPress_(event) {\n        let method;\n        let argument;\n\n        if (DOM.isFocusableElement() || this.ws_.isDisabled()) {\n            return;\n        }\n\n        switch (event.which) {\n            case Keys.AV_PAGE:\n                method = this.enable_ ? this.goNext : this.ws_.goNext;\n                break;\n            case Keys.SPACE:\n                if (event.shiftKey) {\n                    method = this.enable_ ? this.goPrev : this.ws_.goPrev;\n                } else {\n                    method = this.enable_ ? this.goNext : this.ws_.goNext;\n                }\n                break;\n            case Keys.RE_PAGE:\n                method = this.enable_ ? this.goPrev : this.ws_.goPrev;\n                break;\n            case Keys.HOME:\n                method = this.ws_.goToSlide;\n                argument = 0;\n                break;\n            case Keys.END:\n                method = this.ws_.goToSlide;\n                argument = this.ws_.maxSlide_ - 1;\n                break;\n            case Keys.DOWN:\n                method = this.ws_.isVertical ? (this.enable_ ? this.goNext : this.ws_.goNext) : null;\n                break;\n            case Keys.UP:\n                method = this.ws_.isVertical ? (this.enable_ ? this.goPrev : this.ws_.goPrev) : null;\n                break;\n            case Keys.RIGHT:\n                method = !this.ws_.isVertical ? (this.enable_ ? this.goNext : this.ws_.goNext) : null;\n                break;\n            case Keys.LEFT:\n                method = !this.ws_.isVertical ? (this.enable_ ? this.goPrev : this.ws_.goPrev) : null;\n                break;\n\n            case Keys.F:\n                if (!event.metaKey && !event.ctrlKey) {\n                    method = this.ws_.fullscreen;\n                }\n\n                break;\n        }\n        if (method) {\n            method.call(this.enable_ ? this : this.ws_, argument);\n            // Prevents Firefox key events.\n            event.preventDefault();\n        }\n    }\n    //单行前进\n    goNext() {\n        const $curSlide = this.curSlide_.el;\n\n        const subBuilded = toArray($curSlide.querySelectorAll('.building'));\n        let list;\n        if (subBuilded.length) {\n            while ((list = subBuilded.shift())) {\n                list = list.classList;\n                list.remove('building');\n                list.add('builded');\n            }\n        }\n        const toBuild = toArray($curSlide.querySelectorAll('.tobuild'));\n\n        if (!toBuild.length) {\n            // 继续下一页\n            this.enable_ = false;\n            this.ws_.goNext();\n\n            return false;\n        }\n\n        const item = toBuild[0];\n        list = item.classList;\n        list.remove('tobuild');\n\n        list.add('building');\n        return true;\n    }\n\n    //单条往后走\n    goPrev() {\n        const $curSlide = this.curSlide_.el;\n\n        const subBuilded = toArray($curSlide.querySelectorAll('.building'));\n\n        let list;\n        let buildingLen = subBuilded.length;\n        let curList;\n\n        if (buildingLen) {\n            while ((list = subBuilded.shift())) {\n                let clist = list.classList;\n                clist.remove('building');\n                clist.add('tobuild');\n                curList = list;\n            }\n        }\n        const builded = toArray($curSlide.querySelectorAll('.builded'));\n\n        if (!builded.length && !buildingLen) {\n            // 继续下一页\n            this.enable_ = false;\n            this.ws_.goPrev();\n            return false;\n        }\n\n        let item = builded.pop();\n        if (item) {\n            if (!curList) {\n                curList = item;\n            }\n            list = item.classList;\n            list.remove('builded');\n            if (buildingLen === 0) {\n                list.add('tobuild');\n                item = builded.pop();\n                if (item) {\n                    item.classList.remove('builded');\n                    item.classList.add('building');\n                }\n            } else {\n                list.add('building');\n            }\n        }\n        return true;\n    }\n    slideBuild_(e) {\n        // 处理现在的内容\n        if (e && e.detail) {\n            const idx = e.detail.currentSlide0;\n            const slide = this.ws_.slides[idx];\n            this.curSlide_ = slide;\n            const buildNode = toArray(slide.el.querySelectorAll('.tobuild,.builded'));\n            if (buildNode.length) {\n                this.enable_ = true;\n                return;\n            }\n        }\n        this.enable_ = false;\n    }\n}\n\nconst emptyArr = [];\n\n//泛数组转换为数组\nfunction toArray(arrayLike) {\n    return emptyArr.slice.call(arrayLike);\n}\n"
  },
  {
    "path": "packages/nodeppt-js/plugins/mermaid.js",
    "content": "import DOM from 'webslides/src/js/utils/dom';\nimport {default as Slide, Events as SlideEvents} from 'webslides/src/js/modules/slide';\n\nexport default class Echarts {\n    constructor(wsInstance) {\n        this.ws_ = wsInstance;\n\n        const mermaidNode = DOM.toArray(this.ws_.el.querySelectorAll('.lang-mermaid'));\n\n        if (mermaidNode.length) {\n            mermaidNode.forEach((node, j) => {\n                const {i} = Slide.getSectionFromEl(node);\n                const slide = wsInstance.slides[i - 1];\n                slide.mermaidInit = false;\n                slide.mermaidNode = node;\n\n                slide.el.addEventListener(SlideEvents.ENABLE, Echarts.onSectionEnter);\n            });\n        }\n    }\n\n    static onSectionEnter(event) {\n        const slide = event.detail.slide || {};\n        const {mermaidNode, mermaidInit} = slide;\n        if (!mermaidInit && window.mermaid) {\n            setTimeout(() => {\n                const theme =\n                    window.pluginsOptions && window.pluginsOptions.mermaid\n                        ? window.pluginsOptions.mermaid.theme\n                        : undefined;\n                mermaid.initialize({\n                    theme: theme ? theme : 'default'\n                });\n                mermaidNode.style.visibility = 'visible';\n                mermaid.init(undefined, mermaidNode);\n                slide.mermaidInit = true;\n            }, 800);\n        }\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-js/plugins/speaker-mode.js",
    "content": "// import Keyboard from 'webslides/src/js/plugins/keyboard';\n\nexport default class SpeakerMode {\n    constructor(wsInstance) {\n        this.ws_ = wsInstance;\n        this.init_();\n    }\n    bindEvent_() {\n        window.addEventListener('message', this.evtHandler_, false);\n        document.addEventListener('keydown', this.onKeyPress_.bind(this), false);\n    }\n    onKeyPress_(e) {\n        if ((e.detail && e.detail.salt) || !this.listener_) {\n            // 处理过了，防止事件重复处理\n            return;\n        }\n        this.listener_.postMessage({which: e.which, shiftKey: e.shiftKey}, '*');\n    }\n    evtHandler_(e) {\n        // 发送自定义事件\n        const data = e.data;\n        const {which, shiftKey} = data;\n        const event = new CustomEvent('control:keydown', {\n            detail: {\n                salt: true\n            }\n        });\n        event.which = which;\n        event.shiftKey = shiftKey;\n        document.dispatchEvent(event);\n    }\n    init_() {\n        const params = parseQuery();\n        if (params.mode === 'speaker') {\n            this.ws_.el.classList.add('with-note');\n            const url = location.href.replace('mode=speaker', 'mode=audience');\n\n            const sWidth = screen.width;\n            const sHeight = screen.height;\n            const tWidth = sWidth * 0.8;\n            const tHeight = sHeight * 0.8;\n\n            const temp =\n                `height=${tHeight},width=${tWidth},top=10,left=${(sWidth - tWidth) / 2}` +\n                ',toolbar=no,menubar=no,location=yes,resizable=yes,scrollbars=no,status=no';\n\n            this.listener_ = this.popup_ = window.open(url, 'ppt', temp);\n            window.addEventListener('beforeunload', this.closeClient_.bind(this), false);\n            this.bindEvent_();\n        } else if (params.mode === 'audience') {\n            this.listener_ = window.opener;\n            this.bindEvent_();\n        }\n    }\n    closeClient_() {\n        if (this.popup_ && this.popup_.close) {\n            this.popup_.close();\n        }\n    }\n}\n\nfunction parseQuery(url) {\n    let back = {};\n    (url || location.search.substring(1)).split('&').forEach(v => {\n        v = v.split('=');\n        back[v[0].toLowerCase()] = v[1];\n    });\n    return back;\n}\n"
  },
  {
    "path": "packages/nodeppt-js/plugins/speaker-note.js",
    "content": "import DOM from 'webslides/src/js/utils/dom';\nimport {default as Slide, Events as SlideEvents} from 'webslides/src/js/modules/slide';\nconst HEIGHT = 0.2;\nconst SCALE = 1.5;\nexport default class SpeakerNote {\n    constructor(wsInstance) {\n        this.ws_ = wsInstance;\n        this.isSpeakerMode_ = this.ws_.el.classList.contains('with-note');\n\n        const $note = DOM.toArray(this.ws_.el.querySelectorAll('.speaker-note'));\n\n        if ($note.length) {\n            $note.forEach((note, j) => {\n                const {i} = Slide.getSectionFromEl(note);\n                const slide = wsInstance.slides[i - 1];\n                slide.noteNode = note;\n                if (!this.isSpeakerMode_) {\n                    slide.el.addEventListener(SlideEvents.ENABLE, SpeakerNote.onSectionEnter);\n                    slide.el.addEventListener(SlideEvents.DISABLE, SpeakerNote.onSectionDisabled);\n                }\n            });\n        }\n    }\n\n    static toggleNote(e) {\n        // console.log(e);\n        if (e.which === 110 && !e.metaKey && !e.ctrlKey && !e.shiftKey) {\n            // toggleNote\n            this.classList.toggle('show');\n        }\n    }\n    static onSectionDisabled(event) {\n        const $slide = event.detail.slide;\n        document.removeEventListener('keypress', SpeakerNote.toggleNote.bind($slide.noteNode));\n    }\n    static onSectionEnter(event) {\n        const $slide = event.detail.slide;\n        document.addEventListener('keypress', SpeakerNote.toggleNote.bind($slide.noteNode));\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-parser/.npmignore",
    "content": "__tests__\n__mocks__\n"
  },
  {
    "path": "packages/nodeppt-parser/__tests__/classes.md",
    "content": "\ntitle: nodeppt Classes 演示\nspeaker: 三水清\nurl: https://github.com/ksky521/nodeppt\ncss:\n - https://fonts.googleapis.com/css?family=Roboto:100,100i,300,300i,400,400i,700,700i%7CMaitree:200,300,400,600,700&subset=latin-ext\n\n<slide :class=\"size-50\">\n\n##  :fa-heart-o: CSS Syntax\n\nWebSlides is so easy to understand and love. Baseline\\: 8. {.text-intro}\n\n* :Typography\\::{.text-label}  .text-landing, .text-subtitle, .text-data, .text-intro...\n* :BG Colors\\::{.text-label}  .bg-primary, .bg-blue,.bg-apple...\n* :BG Images\\::{.text-label} .background, .background-center-bottom...\n* :Cards\\::{.text-label} .card-60, .card-50, .card-40...\n* :Sizes\\::{.text-label} .size-50, .size-40...\n* :Flex Blocks\\::{.text-label} .flexblock.clients, .flexblock.gallery, .flexblock.metrics...\n{.description}\n\n<slide>\n\n:::column {.vertical-align}\n### **WebSlides is really easy**\nEach parent `<section>` in the #webslides element is an individual slide. {.text-intro}\n\nCode is neat, scalable, and well documented. It uses **intuitive markup with popular naming conventions**. There's no need to overuse classes or nesting. **Based on** [SimpleSlides](https://github.com/jennschiffer/SimpleSlides) , by [Jenn Schiffer](http://jennmoney.biz) :)\n\n----\n```html\n<article id=\"webslides\">\n  <!-- Slide 1 -->\n  <section>\n    <h1>Design for trust</h1>\n  </section>\n  <!-- Slide 2 -->\n  <section class=\"bg-primary\">\n    <div class=\"wrap\">\n      <h2>.wrap = container (width: 90%) with fadein</h2>\n    </div>\n  </section>\n</article>\n```\n:::\n\n---\nVertical sliding? `<article id=\"webslides\" class=\"vertical\">` {.aligncenter}\n\n\n<slide>\n\n:::{.aligncenter}\n### Simple CSS Alignments\n\nPut content wherever you want.\n:::\n\n:::footer\nFooter: logo, credits... (.alignleft) {.alignleft}\n\n[:fa-twitter: @username .alignright](){.alignright}\n\n:::\n\n:::header\nHeader (logo) :.alignright:{.alignright}\n:::\n\n\n<slide>\n\n!![](https://webslides.tv/static/images/iphone.png .size-50.alignleft)\n\n## img.alignleft\n`img.alignleft.size-50`\n\nJobs unveiled the iPhone to the public on January 9, 2007, at the Macworld 2007 convention at the Moscone Center in San Francisco.  Apple sold 6.1 million first generation iPhone units over five quarters.\n\n**Image size recommended**:<br> 800x600px / 600x450px.\n\n\n<slide>\n\n!![](https://webslides.tv/static/images/iphone.png .size-50.alignright)\n\n## img.alignright\n`img.alignright.size-50`\n\nJobs unveiled the iPhone to the public on January 9, 2007, at the Macworld 2007 convention at the Moscone Center in San Francisco.  Apple sold 6.1 million first generation iPhone units over five quarters.\n\n**Image size recommended**:<br> 800x600px / 600x450px.\n\n<slide>\n\n!![](https://webslides.tv/static/images/iphone.png .size-40.aligncenter)\n\n`img.aligncenter.size-40` {.aligncenter}\n\n\n<slide class=\"slide-top\">\n:::{.content-left}\n### 1/9 left top\nPut content wherever you want. Have less. Do more. Create beautiful solutions.\n\n`.slide-top and .content-left`\n\n\n<slide class=\"slide-top\">\n:::{.content-center}\n### 2/9 center top\nIn a village of La Mancha, the name of which I have no desire to call to mind,\n\n`.slide-top and .content-center`\n\n<slide class=\"slide-top\">\n:::{.content-right}\n### 3/9 right top\nthere lived not long since one of those gentlemen that keep a lance in the lance-rack, an old buckler, a lean hack, and a greyhound for coursing.\n\n`.slide-top and .content-right`\n\n\n<slide>\n:::{.content-left}\n### 4/9 left top\nAn olla of rather more beef than mutton, a salad on most nights, scraps on Saturdays,\n\n`.content-left`\n\n<slide>\n:::{.content-center}\n### 5/9 center top\nlentils on Fridays, and a pigeon or so extra on Sundays, made away with three-quarters of his income.\n\n`.content-center`\n\n<slide>\n:::{.content-right}\n### 6/9 right top\nhe rest of it went in a doublet of fine cloth and velvet breeches and shoes to match for holidays,\n\n`.content-right`\n\n\n<slide class=\"slide-bottom\">\n:::{.content-left}\n### 7/9 left bottom\nwhile on week-days he made a brave figure in his best homespun.\n\n`.slide-bottom` and `.content-left`\n\n\n<slide class=\"slide-bottom\">\n:::{.content-center}\n### 8/9 center bottom\nHe had in his house a housekeeper past forty, a niece under twenty, and a lad for the field and market-place,\n\n`.slide-bottom` and `.content-center`\n\n<slide class=\"slide-bottom\">\n:::{.content-right}\n### 9/9 right bottom\nwho used to saddle the hack as well as handle the bill-hook.\n\n`.slide-bottom` and `.content-right`\n\n\n<slide class=\"aligncenter\">\n## .grid + .column\nBasic Grid (auto-fill and equal height). {.text-intro}\n\n:::column\n\n###### Why WebSlides?\n\nThere're excellent presentation tools out there. WebSlides is about good karma and sharing content. Hypertext, clean code, and beauty as narrative elements.\n\n\n----\n\n!![figure](https://webslides.tv/static/images/setup.png .aligncenter)\n\n---\n###### How easy is WebSlides?\nYou can create your own presentation instantly. Just a basic knowledge of HTML and CSS is required. Simply choose a demo and customize it.\n\n:::\n\n<slide class=\"aligncenter\">\n## .grid.**vertical-align** + .column\nBasic Grid (auto-fill and equal height). {.text-intro}\n\n:::column {.vertical-align}\n\n###### Why WebSlides?\n\nThere're excellent presentation tools out there. WebSlides is about good karma and sharing content. Hypertext, clean code, and beauty as narrative elements.\n\n\n----\n\n!![figure](https://webslides.tv/static/images/setup.png .aligncenter)\n\n---\n###### How easy is WebSlides?\nYou can create your own presentation instantly. Just a basic knowledge of HTML and CSS is required. Simply choose a demo and customize it.\n\n:::\n\n\n<slide>\n## .grid.**sm**  (sidebar + main)\n----\n\n:::column {.sm}\n\n### .column 1\n\nStendhal syndrome is a psychosomatic disorder that causes rapid heartbeat, dizziness, fainting, confusion and even hallucinations when an individual is exposed to an experience of great personal significance, particularly viewing art.\n\n---\n\n## .column 2\nThe illness is named after the 19th-century French author Stendhal (pseudonym of Marie-Henri Beyle), who described his experience with the phenomenon during his 1817 visit to Florence in his book Naples and Florence: A Journey from Milan to Reggio.\n\nWhen he visited the Basilica of Santa Croce, where Niccolò Machiavelli, Michelangelo and Galileo Galilei are buried, he saw Giotto's frescoes for the first time and was overcome with emotion.\n\n\n:::\n\n\n<slide>\n## .grid.**ms**  ( main + sidebar)\n----\n\n:::column {.ms}\n\n### .column 1\nThe illness is named after the 19th-century French author Stendhal (pseudonym of Marie-Henri Beyle), who described his experience with the phenomenon during his 1817 visit to Florence in his book Naples and Florence: A Journey from Milan to Reggio.\n\nWhen he visited the Basilica of Santa Croce, where Niccolò Machiavelli, Michelangelo and Galileo Galilei are buried, he saw Giotto's frescoes for the first time and was overcome with emotion.\n\n\n---\n\n## .column 2\n\nStendhal syndrome is a psychosomatic disorder that causes rapid heartbeat, dizziness, fainting, confusion and even hallucinations when an individual is exposed to an experience of great personal significance, particularly viewing art.\n\n:::\n\n\n<slide>\n## .grid.**sms**  ( sidebar + main + sidebar)\n----\n\n:::column {.sms}\n\n### .column 1\n\nInformation architecture is considered to have been founded by Richard Saul Wurman.\n\n----\n### .column 2\n\nInformation architecture (IA) is the structural design of shared information environments; the art and science of organizing and labelling websites, intranets, online communities and software to support usability and findability; and an emerging community of practice focused on bringing principles of design and architecture to the digital landscape.\n\n\n---\n\n## .column 3\nThe difficulty in establishing a common definition for \"information architecture\" arises partly from the term's existence in multiple fields.\n\n:::\n\n<slide>\n\n:::card\n\n\n## Unsplash\n.card-50.bg-white\n\n [Unsplash](http://Unsplash.com) is a really cool resource. It is a collection of Creative Commons Zero licensed photos that are really great. {.text-intro}\n\n* :Role\\::{.text-label} Frontend\n* :client\\::{.text-label} Acme\n* :year\\::{.text-label} 2018\n{.description}\n\n---\n![](https://source.unsplash.com/rCOWMC8qf8A/)\n\n:::\n\n\n<slide>\n\n:::card-50\n\n![](https://source.unsplash.com/HoevDVvxInw/960x720)\n\n---\n\n## Bonsai\n\nBonsai is a Japanese art form using trees grown in containers — .fullscreen > .card-50. {.text-intro}\n\nSimilar practices exist in other cultures, including the Chinese tradition of penjing from which the art originated, and the miniature living landscapes of Vietnamese hòn non bộ.\n\n\\* \\* \\* {.text-symbols}\n\nSimilar practices exist in other cultures, including the Chinese tradition of penjing from which the art originated, and the miniature living landscapes of Vietnamese hòn non\n:::\n\n\n<slide class=\"fullscreen\">\n\n:::card\n\n![](https://source.unsplash.com/ALtNa-uKy3M/)\n\n---\n\n## Bonsai\n\nBonsai is a Japanese art form using trees grown in containers — .fullscreen > .card-50. {.text-intro}\n\nSimilar practices exist in other cultures, including the Chinese tradition of penjing from which the art originated, and the miniature living landscapes of Vietnamese hòn non bộ.\n\n:::\n\n<slide class=\"bg-apple aligncenter\">\n\n# Backgrounds\n\n&lt;slide class=\"bg-apple\"&gt;\n\n\n<slide>\n## Corporate Backgrounds\n:::flexblock {.blink.border}\n\n## .bg-primary {..bg-primary}\n\n\\#44d\n\n----\n\n## .bg-secondary {..bg-secondary}\n\n\\#67d\n\n----\n\n## .bg-light {..bg-light}\n\n\\#edf2f7\n\n----\n\n## body\n\n\\#f7f9fb\n\n:::\n\n----\n\n## General Colors\n:::flexblock {.blink.border}\n\n## .bg-black {..bg-black}\n\n\\#111\n\n----\n\n## .bg-black-blue {..bg-black-blue}\n\n\\#123\n\n----\n\n## .bg-white {..bg-white}\n\n\\#fff\n:::\n\n<slide>\n\n## Colorful\n:::flexblock {.border.blink}\n\n## .bg-red {..bg-red}\n\n\\#c23\n\n----\n\n## .bg-green {..bg-green}\n\n\\#077\n\n----\n\n## .bg-blue {..bg-blue}\n\n\\#346\n\n----\n\n## .bg-purple {..bg-purple}\n\n\\#62b\n\n:::\n\n----\n\n### Transparent Backgrounds\n\n:::flexblock {.border.blink}\n\n## .bg-trans-dark {..bg-trans-dark}\n\nrgba(0, 0, 0, 0.5)\n\n----\n\n## .bg-trans-light {..bg-trans-light}\n\nrgba(255, 255, 255, 0.2)\n\n:::\n\n<slide class=\"bg-gradient-h\">\n# Gradients\n\n:::flexblock {.border}\n\nHorizontal\n`.bg-gradient-h`\n\n----\n\n Radial\n`.bg-gradient-r`\n\n----\nVertical\n`.bg-gradient-v`\n:::\n\n<slide class=\"bg-gradient-v aligncenter\">\n## Vertical Gradient\n\n`.bg-gradient-v`\n\n<slide class=\"bg-gradient-r aligncenter\">\n## Radial Gradient\n\n`.bg-gradient-r`\n\n<slide class=\"bg-black\" video=\"https://webslides.tv/static/videos/working.mp4 poster='https://webslides.tv/static/images/working.jpg'\" >\n\n\n`.background-video`\n\n## **WebSlides is the easiest way to make HTML presentations. Inspire and engage.**\n\n<slide class=\"bg-blue aligncenter\" video=\"https://webslides.tv/static/videos/working.mp4 poster='https://webslides.tv/static/images/working.jpg' .dark\">\n\n\n## BG Video with Overlay {.text-landing}\n\n`<slide class=\"bg-blue aligncenter\" video=\"https://webslides.tv/static/videos/working.mp4 poster='https://webslides.tv/static/images/working.jpg' .dark\">` or `.light`\n\n<slide image=\"https://webslides.tv/static/images/iphone-hand.png .right-bottom\">\n\n:::{.content-left}\n### .background-(position)\n\n:::flexblock {.specs}\n::fa-wifi::\n\n## Ultra-Fast WiFi\nSimple and secure file sharing.\n\n---\n::fa-battery-full::\n\n## All day battery life\nYour battery worries may be over.\n\n---\n::fa-life-ring::\n## All day battery life\nWe'll fix it or if we can't, we'll replace it.\n\n:::\n\n<slide class=\"bg-black aligncenter\" image=\"https://source.unsplash.com/UJbHNoVPZW0/ .dark\">\n\n# Iceland{.text-landing.text-shadow}\n\n`slide[class*=\"bg-\"] > .background.dark`\n\n<slide class=\"bg-black aligncenter\" image=\"https://source.unsplash.com/UJbHNoVPZW0/ .light\">\n\n# Iceland{.text-landing.text-shadow}\n\n`slide[class*=\"bg-\"] > .background.light`\n\n<slide class=\"bg-black aligncenter\" image=\"https://source.unsplash.com/n9WPPWiPPJw/ .anim\">\n\n## .background.anim\n\n\n\n<slide class=\"aligncenter\">\n\n## **Flexible blocks**\n\n`:::flexblock` = Flexible blocks with auto-fill and equal height.\n\n---\n\n:::flexblock\n## :fa-bar-chart: Purpose\nBusinesses that people love1\n\n---\n\n## :fa-bar-chart: Purpose\nBusinesses that people love2\n\n---\n\n## :fa-balance-scale: Purpose\nBusinesses that people love3\n\n---\n\n\n## :fa-cog: Purpose\nBusinesses that people love4\n:::\n\n<slide>\n\n## flexblock\n:::flexblock\n## :fa-bar-chart: Purpose\nBusinesses that people love1\n\n---\n\n## :fa-bar-chart: Purpose\nBusinesses that people love2\n\n---\n\n## :fa-balance-scale: Purpose\nBusinesses that people love3\n\n---\n\n\n## :fa-cog: Purpose\nBusinesses that people love4\n:::\n\n\n## flexblock\n\n`{.blink.border}`\n\n:::flexblock {.blink.border}\n## :fa-bar-chart: Purpose\nBusinesses that people love5\n\n---\n\n## :fa-bar-chart: Purpose\nBusinesses that people love6\n\n---\n\n## :fa-balance-scale: Purpose\nBusinesses that people love7\n\n---\n\n\n## :fa-cog: Purpose\nBusinesses that people love8\n\n:::\n\n<slide>\n\n## flexblock\n\n`{.blink.border}`\n\n:::flexblock {.blink.border}\n## :fa-bar-chart: Purpose\nBusinesses that people love1\n\n---\n\n## :fa-bar-chart: Purpose\nBusinesses that people love2\n\n---\n\n## :fa-balance-scale: Purpose\nBusinesses that people love3\n\n---\n\n\n## :fa-cog: Purpose\nBusinesses that people love4\n\n---\n\n## :fa-bar-chart: Purpose\nBusinesses that people love5\n\n---\n\n## :fa-bar-chart: Purpose\nBusinesses that people love6\n\n---\n\n## :fa-balance-scale: Purpose\nBusinesses that people love7\n\n---\n\n\n## :fa-cog: Purpose\nBusinesses that people love8\n\n:::\n\n\n<slide>\n\n## flexblock clients\n\n`{.clients}`\n\n:::flexblock {.clients}\n\n![](https://webslides.tv/static/images/logos/google.svg){.blacklogo}\n### Interfaces\nCollaboration with the Acme team to design their mobile apps.\n\n---\n![](https://webslides.tv/static/images/logos/microsoft.svg) {.blacklogo}\n\n### Interfaces\nCollaboration with the Acme team to design their mobile apps.\n\n---\n![](https://webslides.tv/static/images/logos/instagram.svg){.blacklogo}\n\n### Interfaces\nCollaboration with the Acme team to design their mobile apps.\n\n---\n![](https://webslides.tv/static/images/logos/netflix.svg){.blacklogo}\n\n\n### Interfaces\nCollaboration with the Acme team to design their mobile apps.\n\n:::\n\n<slide>\n## flexblock clients\n\n`{.clients.border}`\n\n:::flexblock {.clients.border}\n\n![](https://webslides.tv/static/images/logos/google.svg){.blacklogo}\n### Interfaces\nCollaboration with the Acme team to design their mobile apps.\n\n---\n![](https://webslides.tv/static/images/logos/microsoft.svg) {.blacklogo}\n\n### Interfaces\nCollaboration with the Acme team to design their mobile apps.\n\n---\n![](https://webslides.tv/static/images/logos/instagram.svg){.blacklogo}\n\n### Interfaces\nCollaboration with the Acme team to design their mobile apps.\n\n---\n![](https://webslides.tv/static/images/logos/netflix.svg){.blacklogo}\n\n\n### Interfaces\nCollaboration with the Acme team to design their mobile apps.\n\n:::\n\n<slide>\n### ul.flexblock.features\n\n:::flexblock {.features}\n\n## :100 ^%^:  customizable\n\n Well documented\n\n\n ----\n\n:^$^48:\n## EXTRA VIRGIN OLIVE OIL\n\nThe Spanish caviar.\n\n----\n## :fa-wifi: Ultra-fast Wifi\n\nSimple file sharing.\n:::\n\n---\n\n## ul.flexblock.features.blink\n\n:::flexblock {.features.blink}\n\n## :100 ^%^:  customizable\n\n Well documented\n\n\n ----\n\n:^$^48:\n## EXTRA VIRGIN OLIVE OIL\n\nThe Spanish caviar.\n\n----\n## :fa-wifi: Ultra-fast Wifi\n\nSimple file sharing.\n:::\n\n---\n\n<slide class=\"bg-green\">\n\n## flexblock .Metrics\n\n:::flexblock {.border.metrics}\n\nFounded\n::1972::\n\n----\n\n::fa-users::\n\n24M Subscribers\n\n\n---\n\nFounded\n::64%::\n\n----\n\n:~fa-line-chart~:\n\nRevenue: $16M\n\n---\n\n:~fa-building-o~:\n\nCovers, cards, quotes...\n\n----\n\n:~fa-smile-o~:\n\nUse multiples of 8.\n\n---\n\n:~fa-usd~:\n\nFont Awesome Kit.\n\n---\n\n:~fa-university~:\n\nBank: $32M\n\n:::\n\n<slide :class=\"size-60\">\n\n### shadowbox\n\n---\n\n:::shadowbox\n\n## We're web people.\n\nThere're excellent presentation tools out there. WebSlides is about telling the story, and sharing it in a beautiful way. Hypertext and clean code as narrative elements.\n\n---\n\n## Work better, faster.\n\nDesigners, marketers, and journalists can now focus on the content. Simply [choose a demo](https://webslides.tv/demos) and customize it in minutes.\n\n:::\n\n<slide>\n## steps\n\n:::steps\n\n:~fa-file~:\n## Interfaces\n\nWhen you're really passionate about your job, you can change the world.\n\n---\n\n## Interfaces\n\n1. Architecture\n2. Design\n3. Development\n\n---\n\n## Interfaces\n\n1. Architecture\n2. Design\n3. Development\n\n---\n\n## Interfaces\n\n1. Architecture\n2. Design\n3. Development\n\n:::\n\n<slide>\n### gallery\n\n:::gallery\n\n![](https://source.unsplash.com/uPGOEbjbVGA/800x600)\n\n## Alicia Jiménez\n\nFounder & CEO\n\n---\n\n![](https://source.unsplash.com/6anudmpILw4/800x600)\n\n## Sam Trololovitz\n\nMaster of nothing\n\n---\n\n![](https://source.unsplash.com/IFxjDdqK_0U/800x600)\n\n## Erin Gustafson\n\nVP of Design\n\n:::\n\n<slide>\n### gallery overlay\n\n:::gallery-overlay\n\n![](https://source.unsplash.com/uPGOEbjbVGA/800x600)\n\n## Alicia Jiménez\n\nFounder & CEO\n\n---\n\n![](https://source.unsplash.com/zhkTCCmD4xI/800x600)\n\n## Sam Trololovitz\n\nCTO\n\n---\n\n![](https://source.unsplash.com/IFxjDdqK_0U/800x600)\n\n## Erin Gustafson\n\nVP of Design\n\n:::\n\n\n<slide class=\"bg-red\" image=\"https://source.unsplash.com/R1J6Z1cnJZc/ .dark\">\n\n:::cta\n\n!![](https://webslides.tv/static/images/logos/netflix.svg .whitelogo)\n\n---\n\n## Watch TV shows anytime, anywhere\n\n.frame.bg-red\n\n:::\n\n<slide class=\"bg-red frame\">\n\n:::cta\n\n::^$^40::\n\n---\n\n## Watch TV shows anytime, anywhere\n\n.frame.bg-red\n\n:::\n\n<slide class=\"aligncenter\">\n# Landings {.text-landing}\n\n`.text-landing`\n\n<slide class=\"aligncenter\">\n# Landings {.text-landing}\n\nCreate a simple web presence. {.text-intro}\n\n`.text-intro`\n\n<slide class=\"aligncenter\">\n\nPOWERED BY [#WEBSLIDES](https://twitter.com/search?f=tweets&vertical=default&q=%23WebSlides&src=typd) `.text-subtitle` {.text-subtitle}\n\n# Landings {.text-landing}\n\nCreate a simple web presence. {.text-intro}\n\n`.text-intro`\n\n<slide class=\"bg-black aligncenter\" image=\"https://source.unsplash.com/C1HhAQrbykQ/\">\n# **Landings** {.text-landing.text-shadow}\n\n`.text-shadow` {.text-intro}\n\n<slide class=\"bg-apple aligncenter\">\n## 4,235,678 {.text-data}\n\n`.text-data`\n\n<slide>\n\nWhy WebSlides? .text-context {.text-content}\n\n## WebSlides is incredibly easy and versatile. The easiest way to make HTML presentations.\n\n<slide>\n\n`.text-cols (2 columns)`\n\n:::div {.text-cols}\n\n**Why WebSlides?** There are excellent presentation tools out there. WebSlides is about sharing content, essential features, and clean markup. **Each parent &lt;slide&gt;**  in the #webslides element is an individual slide.\n\n**WebSlides help you build a culture of innovation and excellence**. When you're really passionate about your job, you can change the world. How to manage a design-driven organization? Leadership through usefulness, openness, empathy, and good taste.\n\n:::\n\n:::flexblock {.metrics}\n\n:fa-phone:\n\nCall us at 555.345.6789\n\n----\n\n:fa-twitter:\n\n@username\n\n----\n:fa-envelope:\nSend us an email\n:::\n\n<slide>\n:::column {.vertical-align}\n\n## A Phone by Google\nPixel's camera lets you take brilliant photos in low light, bright light or any light. {.text-intro}\n\n* :Typography\\::{.text-label}  .text-landing, .text-subtitle, .text-data, .text-intro...\n* :BG Colors\\::{.text-label}  .bg-primary, .bg-blue,.bg-apple...\n* :BG Images\\::{.text-label} .background, .background-center-bottom...\n* :Sizes\\::{.text-label} .size-50, .size-40...\n* :Flex Blocks\\::{.text-label} .flexblock.clients, .flexblock.gallery, .flexblock.metrics...\n{.description}\n\n----\n![](https://webslides.tv/static/images/android.png)\n\n:::\n\n<slide class=\"aligncenter text-serif\">\n\n:::div {.content-left}\n## WebSlides is incredibly easy and versatile.\n`.text-serif`  (Maitree)\n:::\n\n:::div {.content-left}\n\nEach parent `<slide>` in the #webslides element is an individual slide.\n\nClean markup with popular naming conventions. Minimum effort. Just focus on your content.\n\n:::\n\n<slide :class=\"size-50\">\n\n### **What is Stendhal Syndrome?**\n\nBeauty overdose. `.text-pull-right` {.text-intro}\n\nImagine that you are in Florence. If you suddenly start to feel that you literally cannot breathe, you may be experiencing Stendhal Syndrome.\n\nPsychiatrists have long debated whether it really exists. {.text-pull-right}\n\nThe syndrome is not only associated with viewing a beautiful place, but also good art.\n\nThe beauty of Italian art has a concentrated perfection and transcendent sensuality that is incredibly addictive.\n\n<slide class=\"bg-primary\">\n# Design :for: understanding\n:::flexblock {.features.fadeInUp}\n\n:100^%^: purpose\n## Businesses that people love\n\n---\n## :fa-heart-o: Principles\n\nUseful → Easy → Fast → Beautiful\n:::\n\n<slide image=\"https://source.unsplash.com/yssUhIxbUZA/\">\n\n::: div {.content-left.bg-trans-dark.fadeInUp}\n!![](https://webslides.tv/static/images/logos/airbnb.svg .whitelogo)\n\n---\n\n## **Designing Experiences**\n\nMeet locals who share your interests.\n:::\n\n<slide class=\"bg-black slide-bottom\" image=\"https://source.unsplash.com/RSOxw9X-suY/\">\n\n:::div {.content-left}\n:fa-tree large:\n\n## 1,000,000\n### We're working to protect up to a million acres of sustainable forest.\n\n:::\n\n<slide class=\"bg-black-blue\">\n\n:::column\n### **:fa-line-chart: Interface**\n\nDesign for growth. We've built a team of world-class designers, developers, and managers.\n\n---\n### **:fa-film: Videos**\n\nWe connect your audience needs, business goals, and brand values into a strategy.\n\n---\n### **:fa-users: Recruiting**\n\nWe offer personalized services with deep expertise in design and technology.\n\n---\n### **:fa-graduation-cap: Formation**\n\nWe train teams to help organizations succeed in the digital age.\n:::\n\n\n<slide>\n## table\n\n| Left-aligned | Center-aligned | Right-aligned |\n| :----------- | :------------: | ------------: |\n| git status   |   git status   |    git status |\n| git diff     |    git diff    |      git diff |\n| git status   |   git status   |    git status |\n\n\n<slide class=\"bg-black-blue\" :class=\"size-60\">\n> I have always appreciated designers who dare to reinterpret fabrics and proportions, so I follow the Japanese and Belgian designers.\n> ==Zaha Hadid==\n> {.text-quote}\n\n<slide image=\"https://webslides.tv/static/images/satya.png .left-bottom\">\n\n:::div {.content-right}\n> \"There is something only a CEO uniquely can do, which is set that tone, which can then capture the soul of the collective.\"\n> ==Satya Nadella, CEO of Microsoft.==\n:::\n\n\n<slide>\n:::card {.quote}\n\n\n\n![](https://webslides.tv/static/images/davinci.png)\n\n---\n> “WebSlides helped us build a culture of innovation and excellence.”\n> ==Leonardo da Vinci==\n\n\n\n<slide>\n\n::: {.content-left}\n## button\n\n[.button](){.button} [.button.radius](){.button.radius}\n\n[.button.ghost](){.button.ghost} [:fa-github: svg-icon](){.button}\n:::\n"
  },
  {
    "path": "packages/nodeppt-parser/__tests__/demo.md",
    "content": "title: nodeppt markdown 演示\nspeaker: 三水清\nurl: https://github.com/ksky521/nodeppt\njs:\n    - https://www.echartsjs.com/asset/theme/shine.js\nprismTheme: solarizedlight\nplugins:\n    - echarts\n    - katex\n\n\n\n<slide>\n## Corporate Backgrounds\n:::blink\n\n## .bg-primary {..bg-primary ..href=\"abc\"}\n\n\\#44d\n\n----\n\n## .bg-secondary {..bg-secondary}\n\n\\#67d\n\n----\n\n## .bg-light {..bg-light}\n\n\\#edf2f7\n\n----\n\n## body\n\n\\#f7f9fb\n\n:::\n\n\n<slide>\n## Corporate Backgrounds\n:::flexbox\n\n## .bg-primary {..bg-primary}\n\n\\#44d\n\n----\n\n## .bg-secondary {..bg-secondary}\n\n\\#67d\n\n----\n\n## .bg-light {..bg-light}\n\n\\#edf2f7\n\n----\n\n## body\n\n\\#f7f9fb\n\n:::\n\n----\n\n## General Colors\n:::flexbox\n\n## .bg-black {..bg-black}\n\n\\#111\n\n----\n\n## .bg-black-blue {..bg-black-blue}\n\n\\#123\n\n----\n\n## .bg-white {..bg-white}\n\n\\#fff\n:::\n\n<slide>\n\n## Colorful\n:::flexbox\n\n## .bg-red {..bg-red}\n\n\\#c23\n\n----\n\n## .bg-green {..bg-green}\n\n\\#077\n\n----\n\n## .bg-blue {..bg-blue}\n\n\\#346\n\n----\n\n## .bg-purple {..bg-purple}\n\n\\#62b\n\n:::\n\n----\n\n### Transparent Backgrounds\n\n:::flexbox\n\n## .bg-trans-dark {..bg-trans-dark}\n\nrgba(0, 0, 0, 0.5)\n\n----\n\n## .bg-trans-light {..bg-trans-light}\n\nrgba(255, 255, 255, 0.2)\n\n:::\n\n<slide class=\"bg-gradient-h\">\n# Gradients\n\n:::flexbox {.border}\n\nHorizontal\n`.bg-gradient-h`\n\n----\n\n Radial\n`.bg-gradient-r`\n\n----\nVertical\n`.bg-gradient-v`\n:::\n\n<slide class=\"bg-gradient-v aligncenter\">\n## Vertical Gradient\n\n`.bg-gradient-v`\n\n<slide class=\"bg-gradient-r aligncenter\">\n## Radial Gradient\n\n`.bg-gradient-r`\n\n<slide class=\"bg-apple aligncenter\">\n## One more background :)\n\n`.bg-apple`\n\n\n\n<slide class=\"bg-purple aligncenter\" image=\"https://source.unsplash.com/C1HhAQrbykQ/ .dark\">\n\n# nodeppt\n\n## 这可能是迄今为止最好的网页版演示库\n\n<slide class=\"bg-blue aligncenter\" video='https://webslides.tv/static/videos/peggy.mp4 .dark poster=\"https://webslides.tv/static/images/peggy.jpg\"'>\n\n# 为什么选择 nodeppt\n\n`section.bg-blue > .background-video.dark` or `.light`\n\n\n<slide class=\"\" :class=\"size-40\">\n\n### 为什么选择 nodeppt\n\n- 基于 GFM 的 markdown 语法编写\n- 支持 html 混排，再复杂的 demo 也可以做！\n- 导出网页或者 pdf 更容易分享\n- 支持单页背景图片\n- 多种模式：纵览模式，双屏模式，远程控制\n- 可以使用画板，可以使用 note 做备注\n- 支持语法高亮，自由选择 highlight 样式\n- 可以单页 ppt 内部动效，单步动效\n- 支持进入/退出回调，做在线 demo 很方便\n{.build.fadeIn}\n\n<slide>\n\n## 为什么选择 nodeppt\n\n:::column {.vertical-align}\n\n- 基于 GFM 的 markdown 语法编写\n- 支持 html 混排，再复杂的 demo 也可以做！\n- 导出网页或者 pdf 更容易分享\n- 支持单页背景图片\n- 多种模式：纵览模式，双屏模式，远程控制\n- 可以使用画板，可以使用 note 做备注\n- 支持语法高亮，自由选择 highlight 样式\n- 可以单页 ppt 内部动效，单步动效\n- 支持进入/退出回调，做在线 demo 很方便\n\n---\n```html {..fadeInUp..slow}\n<article id=\"webslides\">\n  <!-- Slide 1 -->\n  <section>\n    <h1>Design for trust</h1>\n  </section>\n  <!-- Slide 2 -->\n  <section class=\"bg-primary\">\n    <div class=\"wrap\">\n      <h2>.wrap = container (width: 90%)</h2>\n    </div>\n  </section>\n</article>\n```\n:::\n\n<slide class=\"frame moveIn\" :class=\"size-60 bg-white tobuild\">\n\n### :fa-info-circle large: **Autoplay Feature**\n\nAutoplay is generally disabled on all mobile devices to prevent bandwidth consumption. User must execute the play manually. {.text-intro}\n\n\n<slide>\n\n## Header & Footer\n\n:::header {.abeee}\n### Header\n:::\n\n\n:::footer\n### Footer\n\n:::\n\n:::note\n### Note\n\n:::\n\n<slide class=\"bg-brown\">\n\n:::column\n\n::fa-heart large::\n\n### **Feature 1**\n\nTest your web and mobile designs, and quickly incorporate user feedback.\n\n---\n\n::fa-heart large::\n\n### **Feature 1**\n\nTest your web and mobile designs, and quickly incorporate user feedback.\n\n---\n\n::fa-heart large::\n\n### **Feature 1**\n\nTest your web and mobile designs, and quickly incorporate user feedback.\n\n:::\n\n<slide>\n\n::: {.content-left}\n## button\n\n[.button](){.button} [.button.radius](){.button.radius}\n\n[.button.ghost](){.button.ghost} [:fa-github: svg-icon](){.button}\n:::\n\n\n\n<slide :class=\"size-50\">\n### Let's check out some examples.\nAll content is for demo purposes only.\n\n---\n\n1. Welcomes\n2. Covers\n3. Abouts & Teams\n4. Features & Benefits\n5. Cards\n6. Metrics & Data\n7. Pricing & Offers\n8. Quotes\n9. Buttons & Badges\n10. Forms\n11. SVG Icons\n12. Logos\n13. CSS Animations\n14. Embedding videos, maps, charts...\n{.text-cols}\n\n<slide class=\"aligncenter\">\n\n## Welcomes {.text-landing}\n\n**WebSlides** is an open source tool for telling stories. {.text-intro}\n\n<nav>\n* [Twitter](https://twitter.com/webslides)\n* [Dribbble](https://twitter.com/webslides)\n* [Github](https://twitter.com/webslides)\n</nav>\n\n<slide class=\"bg-secondary\" :class=\"size-50 frame\">\n\n## How to Tell Your Story? {.text-serif.aligncenter}\n\n\\* \\* \\* {.text-symbols}\n\nStories have the power to change the world. WebSlides helps you write better content, faster. Your slides are there to support your story. Choose words wisely, create meaning with them, keep it simple.\n\n<slide :class=\"size-60\">\n\n### **Why WebSlides**? Good karma and productivity.\n\n---\n\n:::shadowbox\n\n## We're web people.\n\nThere're excellent presentation tools out there. WebSlides is about telling the story, and sharing it in a beautiful way. Hypertext and clean code as narrative elements.\n\n---\n\n## Work better, faster.\n\nDesigners, marketers, and journalists can now focus on the content. Simply [choose a demo](https://webslides.tv/demos) and customize it in minutes.\n\n:::\n\n<slide image=\"https://source.unsplash.com/Vti8XHv2XjU/\" class=\"bg-black aligncenter\">\n# **California** {.text-shadow}\n\n<slide class=\"bg-gradient-v\" :class=\"size-60\" image=\"https://source.unsplash.com/nxfuA21kNHY/1440x1440 .dark\">\n\nGOOD KARMA {.text-context}\n\n## WebSlides is about **telling the story**, and sharing it in a beautiful way.\n\n<slide class=\"bg-black aligncenter\" image=\"https://source.unsplash.com/mGYxAWITqMg/\">\n\nPlan your next trip {.text-subtitle}\n\n# Summ.er {.text-shadow}\n\nThe best places at the best price. {.text-intro}\n\n<slide class=\"bg-black\" image=\"https://source.unsplash.com/7waHOTcvcT4/\">\n\n\\$975 {.text-data}\n\n<slide class=\"bg-black slide-bottom\" image=\"https://source.unsplash.com/Q1p7bh3SHj8/\">\n\nLocation Intelligence {.text-subtitle}\n\n## **The application of geographic mapping to data**\n\n\n<slide image=\"https://source.unsplash.com/YMOHw3F1Hdk/\">\n\n::: {.alignright.size-50.bg-trans-dark}\nNew in London {.text-subtitle.text-serif}\n### **Hotel Daenerys**\n\nThe Daenerys has facilities such as a 24-hour front desk, an elevator with access to all rooms, and a terrace with a garden where guests can enjoy breakfast during the summer.\n\n[More info]()\n\n:::\n\n\n<slide :class=\"aligncenter\">\n\n## **Abouts & Teams**\n\n<slide class=\"bg-primary\">\n\n:::flexbox\n\n!![div](https://webslides.tv/static/images/logos/google.svg)\n\n---\n\n!![div](https://webslides.tv/static/images/logos/netflix.svg)\n\n---\n\n!![div](https://webslides.tv/static/images/logos/microsoft.svg)\n\n:::\n\n<slide>\n\n::: {.content-center}\n### ul.flexblock.specs\n\n:::specs\n\n## :fa-long-arrow-right: SIMPLE NAVIGATION\nwith arrow keys and swipe.\n\n----\n\n## :fa-link: Permalinks\nGo to a specific slide. URL: #slide=number\n\n---\n\n## :fa-text-height: SVG ICONS\nFont Awesome Kit.\n\n:::\n:::\n\n<slide class=\"bg-red\" image=\"https://source.unsplash.com/R1J6Z1cnJZc/ .dark\">\n\n:::cta\n\n!![](https://webslides.tv/static/images/logos/netflix.svg .whitelogo)\n\n---\n\n## Watch TV shows anytime, anywhere\n\n.frame.bg-red\n\n:::\n\n<slide class=\"bg-red frame\">\n\n:::cta\n\n::^$^40::\n\n---\n\n## Watch TV shows anytime, anywhere\n\n.frame.bg-red\n\n:::\n\n<slide class=\"bg-dark\">\n\n## Flexbox\n\n:::flexbox\n\nFounded\n\n----\n\n\n24M Subscribers\n\n\n---\n\nFounded\n\n----\n\n\nRevenue: $16M\n\n---\n\n\nCovers, cards, quotes...\n\n----\n\n\nUse multiples of 8.\n\n---\n\n\nFont Awesome Kit.\n\n---\n\n\nBank: $32M\n\n:::\n\n<slide>\n\n<slide class=\"bg-green\">\n\n## Metrics\n\n:::flexbox {.border.metrics}\n\nFounded\n::1972::\n\n----\n\n::fa-users::\n\n24M Subscribers\n\n\n---\n\nFounded\n::64%::\n\n----\n\n:~fa-line-chart~:\n\nRevenue: $16M\n\n---\n\n:~fa-building-o~:\n\nCovers, cards, quotes...\n\n----\n\n:~fa-smile-o~:\n\nUse multiples of 8.\n\n---\n\n:~fa-usd~:\n\nFont Awesome Kit.\n\n---\n\n:~fa-university~:\n\nBank: $32M\n\n:::\n\n<slide>\n\n## Features\n\n:::features\n\n## ::→:: SIMPLE NAVIGATION\nwith arrow keys and swipe.\n\n----\n\n## :fa-link: Permalinks\n\nGo to a specific slide.\n\n---\n\n## :fa-clock-o: Permalinks\n\nGo to a specific slide.\n\n----\n\n## ::40+:: BEAUTIFUL COMPONENTS\nCovers, cards, quotes...\n\n----\n\n## :fa-text-height: VERTICAL RHYTHM\nUse multiples of 8.\n\n---\n\n## ::500+:: SVG ICONS\nFont Awesome Kit.\n\n:::\n\n<slide>\n## About/Services/Clients\n\n`ul.flexblock.blink.border`\n\n:::blink\n\n## Interfaces\n\nWhen you're really passionate about your job, you can change the world.\n\n---\n\n## Interfaces\n\n1. Architecture\n2. Design\n3. Development\n\n---\n\n## Interfaces\n\n1. Architecture\n2. Design\n3. Development\n\n---\n\n## Interfaces\n\n1. Architecture\n2. Design\n3. Development\n\n---\n\n## Interfaces\n\nWhen you're really passionate about your job, you can change the world.\n\n---\n\n!![div .abc](https://webslides.tv/static/images/logos/google.svg .aligncenter.graylogo)\n\nAcme hired us to help make the reading experience totally engaging.\n\n---\n\n\n!![div](https://webslides.tv/static/images/logos/google.svg .aligncenter.blacklogo)\n\nAcme hired us to help make the reading experience totally engaging.\n\n---\n\n## Interfaces\n\n1. Architecture\n2. Design\n3. Development\n\n:::\n\n<slide>\n## ul.flexblock.steps\n\n:::steps\n\n:~fa-file~:\n## Interfaces\n\nWhen you're really passionate about your job, you can change the world.\n\n---\n\n## Interfaces\n\n1. Architecture\n2. Design\n3. Development\n\n---\n\n## Interfaces\n\n1. Architecture\n2. Design\n3. Development\n\n---\n\n## Interfaces\n\n1. Architecture\n2. Design\n3. Development\n\n:::\n\n<slide>\n\n:::column\n\n### FAQs {.text-context}\n\nWebSlides is an open source solution by\n\n---\n\n###### Why WebSlides?\n\nThere are excellent presentation tools out there. WebSlides is about good karma and sharing content. Hypertext, clean code, and beauty as narrative elements.\n\n\\* \\* \\* {.text-symbols}\n\n###### Is WebSlides a framework?\n\nWe're all tired of heavy CSS frameworks. WebSlides is a starting point that provides basic\n\n---\n\n###### Why WebSlides?\n\nThere are excellent presentation tools out there. WebSlides is about good karma and sharing content. Hypertext, clean code, and beauty as narrative elements.\n\n\\* \\* \\* {.text-symbols}\n\n###### Is WebSlides a framework?\n\nWe're all tired of heavy CSS frameworks. WebSlides is a starting point that provides basic\n\n:::\n\n<slide>\n### Team\n\n:::gallery\n\n![](https://source.unsplash.com/uPGOEbjbVGA/800x600)\n\n## Alicia Jiménez\n\nFounder & CEO\n\n---\n\n![](https://source.unsplash.com/6anudmpILw4/800x600)\n\n## Sam Trololovitz\n\nMaster of nothing\n\n---\n\n![](https://source.unsplash.com/IFxjDdqK_0U/800x600)\n\n## Erin Gustafson\n\nVP of Design\n\n:::\n\n<slide>\n### Team\n\n:::gallery overlay\n\n![](https://source.unsplash.com/uPGOEbjbVGA/800x600)\n\n## Alicia Jiménez\n\nFounder & CEO\n\n---\n\n![](https://source.unsplash.com/zhkTCCmD4xI/800x600)\n\n## Sam Trololovitz\n\nCTO\n\n---\n\n![](https://source.unsplash.com/IFxjDdqK_0U/800x600)\n\n## Erin Gustafson\n\nVP of Design\n\n:::\n\n<slide>\n## echarts {.aligncenter}\n```echarts {style=\"height:400px;\"}\n{\n    tooltip: {\n        trigger: 'item',\n        formatter: \"{a} <br/>{b}: {c} ({d}%)\"\n    },\n    legend: {\n        orient: 'vertical',\n        x: 'left',\n        data:['直达','营销广告','搜索引擎','邮件营销','联盟广告','视频广告','百度','谷歌','必应','其他']\n    },\n    series: [\n        {\n            name:'访问来源',\n            type:'pie',\n            selectedMode: 'single',\n            radius: [0, '30%'],\n\n            label: {\n                normal: {\n                    position: 'inner'\n                }\n            },\n            labelLine: {\n                normal: {\n                    show: false\n                }\n            },\n            data:[\n                {value:335, name:'直达', selected:true},\n                {value:679, name:'营销广告'},\n                {value:1548, name:'搜索引擎'}\n            ]\n        },\n        {\n            name:'访问来源',\n            type:'pie',\n            radius: ['40%', '55%'],\n            label: {\n                normal: {\n                    formatter: '{a|{a}}{abg|}\\n{hr|}\\n  {b|{b}：}{c}  {per|{d}%}  ',\n                    backgroundColor: '#eee',\n                    borderColor: '#aaa',\n                    borderWidth: 1,\n                    borderRadius: 4,\n                    // shadowBlur:3,\n                    // shadowOffsetX: 2,\n                    // shadowOffsetY: 2,\n                    // shadowColor: '#999',\n                    // padding: [0, 7],\n                    rich: {\n                        a: {\n                            color: '#999',\n                            lineHeight: 22,\n                            align: 'center'\n                        },\n                        // abg: {\n                        //     backgroundColor: '#333',\n                        //     width: '100%',\n                        //     align: 'right',\n                        //     height: 22,\n                        //     borderRadius: [4, 4, 0, 0]\n                        // },\n                        hr: {\n                            borderColor: '#aaa',\n                            width: '100%',\n                            borderWidth: 0.5,\n                            height: 0\n                        },\n                        b: {\n                            fontSize: 16,\n                            lineHeight: 33\n                        },\n                        per: {\n                            color: '#eee',\n                            backgroundColor: '#334455',\n                            padding: [2, 4],\n                            borderRadius: 2\n                        }\n                    }\n                }\n            },\n            data:[\n                {value:335, name:'直达'},\n                {value:310, name:'邮件营销'},\n                {value:234, name:'联盟广告'},\n                {value:135, name:'视频广告'},\n                {value:1048, name:'百度'},\n                {value:251, name:'谷歌'},\n                {value:147, name:'必应'},\n                {value:102, name:'其他'}\n            ]\n        }\n    ]\n}\n\n```\n\n\n<slide>\n\n:::card-50\n\n![](https://source.unsplash.com/HoevDVvxInw/960x720)\n\n---\n\n## Bonsai\n\nBonsai is a Japanese art form using trees grown in containers — .fullscreen > .card-50. {.text-intro}\n\nSimilar practices exist in other cultures, including the Chinese tradition of penjing from which the art originated, and the miniature living landscapes of Vietnamese hòn non bộ.\n\n\\* \\* \\* {.text-symbols}\n\nSimilar practices exist in other cultures, including the Chinese tradition of penjing from which the art originated, and the miniature living landscapes of Vietnamese hòn non\n:::\n\n\n<slide class=\"fullscreen\">\n\n:::card\n\n![](https://source.unsplash.com/ALtNa-uKy3M/)\n\n---\n\n## Bonsai\n\nBonsai is a Japanese art form using trees grown in containers — .fullscreen > .card-50. {.text-intro}\n\nSimilar practices exist in other cultures, including the Chinese tradition of penjing from which the art originated, and the miniature living landscapes of Vietnamese hòn non bộ.\n\n\n\\* \\* \\* {.text-symbols}\n\ndfdasfs\n:::\n\n\n<slide class=\"bg-gradient-v\">\n## table\n\n| Left-aligned | Center-aligned | Right-aligned |\n| :----------- | :------------: | ------------: |\n| git status   |   git status   |    git status |\n| git diff     |    git diff    |      git diff |\n\n\n\n<slide class=\"bg-gradient-v\">\n\n## KaTex {.aligncenter}\n\n| equation                                                                                                                                                                  | description                                                                            |\n| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |\n| $\\nabla \\cdot \\vec{\\mathbf{B}}  = 0$                                                                                                                                      | divergence of $\\vec{\\mathbf{B}}$ is zero                                               |\n| $\\nabla \\times \\vec{\\mathbf{E}}\\, +\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{B}}}{\\partial t}  = \\vec{\\mathbf{0}}$                                                          | curl of $\\vec{\\mathbf{E}}$ is proportional to the rate of change of $\\vec{\\mathbf{B}}$ |\n| $\\nabla \\times \\vec{\\mathbf{B}} -\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{E}}}{\\partial t} = \\frac{4\\pi}{c}\\vec{\\mathbf{j}}    \\nabla \\cdot \\vec{\\mathbf{E}} = 4 \\pi \\rho$ | _wha?_                                                                                 |\n\n<slide>\n\n## **Why WebSlides?**\n\n> \"I feel guilty as a web designer when I have to use PowerPoint and Keynote. So I made #WebSlides.\"\n\n\n<slide class=\"bg-black-blue\">\n> I have always appreciated designers who dare to reinterpret fabrics and proportions, so I follow the Japanese and Belgian designers.\n> ==Zaha Hadid==\n> {.text-quote}\n\n\n\n<slide :class=\"aligncenter fadeInUp\">\n## The little things mean the most\n\n.fadeInUp\n\n\n<slide :class=\"aligncenter zoomIn size-40\">\n\n![](https://webslides.tv/static/images/android.png)\n\n<slide :class=\"aligncenter\">\n\n## h2.fadeIn.slow {.fadeIn.slow}\n\n\n"
  },
  {
    "path": "packages/nodeppt-parser/__tests__/public/demo.js",
    "content": "console.log('demo');\n"
  },
  {
    "path": "packages/nodeppt-parser/defaults.js",
    "content": "module.exports = {\n    title: 'nodeppt markdown',\n    url: '',\n    speaker: '',\n    js: '',\n    theme: 'moon',\n    transition: 'move',\n    highlightStyle: 'monokai_sublime',\n    date: Date.now(),\n    htmlWebpackPlugin: `\n  <% for (var chunk in htmlWebpackPlugin.files.chunks) { %>\n    <script src=\"<%= htmlWebpackPlugin.files.chunks[chunk].entry %>\"></script>\n    <% } %>\n  `\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/index.js",
    "content": "const path = require('path');\nconst fs = require('fs-extra');\nconst defaultDeep = require('lodash.defaultsdeep');\nconst ejs = require('ejs');\nconst loaderUtils = require('loader-utils');\n\nconst getParser = require('./lib/get-parser');\n// parsers\nconst yamlParser = require('./lib/yaml-parser');\n\nconst defaults = require('./defaults');\n\n// 模板\nconst defaultTemplate = fs.readFileSync(path.join(__dirname, './template/index.ejs')).toString();\n// 这里不要用箭头函数，this 指向问题！\n/* eslint-disable space-before-function-paren */\nmodule.exports = function (content) {\n    /* eslint-enable space-before-function-paren */\n    const {plugins = []} = loaderUtils.getOptions(this);\n    const resourcePath = this.resourcePath;\n    const parser = getParser(plugins);\n\n    const settings = content.split(/<slide.*>/i)[0];\n    const yamlSettings = yamlParser(settings);\n    // 支持baseTemplate，传入ejs模板\n    let template = defaultTemplate;\n    if (yamlSettings.baseTemplate && typeof yamlSettings.baseTemplate === 'string') {\n        const baseTemplate = path.resolve(path.dirname(resourcePath), yamlSettings.baseTemplate);\n        if (fs.existsSync(baseTemplate)) {\n            template = fs.readFileSync(baseTemplate).toString();\n        }\n    }\n    // 首部 yaml 设置部分\n    const globalSettings = defaultDeep(yamlSettings, defaults);\n    content = parser(content);\n\n    return ejs.render(template, {...globalSettings, content});\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/get-markdown-parser.js",
    "content": "const mdIt = require('markdown-it')();\n// const prism = require('markdown-it-prism');\nconst prism = require('./markdown/prism');\n\nmdIt.use(prism, {\n    defaultLanguageForUnknown: 'textile'\n});\nmdIt.use(require('markdown-it-sup'));\nmdIt.use(require('markdown-it-br'));\nmdIt.use(require('@ksky521/markdown-it-katex'));\n\nmdIt.use(require('./markdown/jsx'));\nmdIt.use(require('./markdown/echarts'));\n// mermaid 流程图\nmdIt.use(require('./markdown/mermaid'));\nmdIt.use(require('./markdown/fa'));\n// mdIt.use(require('./markdown/plus-list'));\nmdIt.use(require('./markdown/link'));\n// 注意attrs顺序\nmdIt.use(require('./markdown/attrs'));\nmdIt.use(require('./markdown/img'));\nmdIt.use(require('./markdown/cite'));\nmdIt.use(require('./markdown/span'));\n\nmdIt.use(require('./markdown/container'), 'column', require('./markdown/containers/column'));\nmdIt.use(require('./markdown/container'), 'shadowbox', require('./markdown/containers/shadow'));\nmdIt.use(require('./markdown/container'), 'steps', require('./markdown/containers/steps'));\nmdIt.use(require('./markdown/container'), 'card', require('./markdown/containers/card'));\n// 不用了\n// mdIt.use(require('./markdown/container'), 'flexbox', require('./markdown/containers/flexbox')('flexbox'));\nmdIt.use(require('./markdown/container'), 'flexblock', require('./markdown/containers/flexblock'));\n// 不用了\n// mdIt.use(require('./markdown/container'), 'blink', require('./markdown/containers/blink'));\n// mdIt.use(require('./markdown/container'), 'features', require('./markdown/containers/features'));\n// 不用了\n// mdIt.use(require('./markdown/container'), 'specs', require('./markdown/containers/specs'));\nmdIt.use(require('./markdown/container'), 'cta', require('./markdown/containers/cta'));\n\nmdIt.use(require('./markdown/container'), 'gallery', require('./markdown/containers/gallery'));\nmdIt.use(require('./markdown/container'), 'div', require('./markdown/containers/div'));\n\nfunction getMdParser(plugins) {\n    plugins.forEach(plugin => {\n        mdIt.use(plugin);\n    });\n    return mdIt.render.bind(mdIt);\n}\n\nmodule.exports = getMdParser;\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/get-parser.js",
    "content": "const posthtml = require('posthtml');\nconst getMdParser = require('./get-markdown-parser');\n// 内置 posthtml 插件\nconst buildInPlugins = [\n    './tags/slide.js',\n    './tags/note.js',\n    './tags/header-footer.js',\n    // attrs放到最后\n    './tags/attrs.js',\n];\nconst buildInPosthtmlPlugins = buildInPlugins.map((file) => {\n    return require(file);\n});\n\nmodule.exports = (plugins) => {\n    const markdownPlugins = [];\n    const posthtmlPlugins = [];\n\n    plugins.forEach((p) => {\n        if (p && typeof p.apply === 'function') {\n            if (p.id.indexOf('markdown') === 0) {\n                markdownPlugins.push(p.apply);\n            } else if (p.id.indexOf('posthtml') === 0) {\n                posthtmlPlugins.push(p.apply);\n            }\n        }\n    });\n    const mdRender = getMdParser(markdownPlugins);\n\n    return (str) => {\n        const slideTag = str.match(/\\n<slide\\s*(.*)>/gim) || [];\n        const contents = str.split(/\\n<slide.*>/im);\n        contents.shift();\n        return contents\n            .map((c, i) => {\n                // 生成 attr\n                const html = `\n${slideTag[i]}\n<div class=\"wrap\" wrap=\"true\">\n${mdRender(c)}\n</div>\n</slide>\n      `;\n                // 生成 content ast\n                return posthtml(buildInPosthtmlPlugins.concat(posthtmlPlugins)).process(html, {sync: true}).html;\n            })\n            .join('\\n');\n    };\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/attrs/index.js",
    "content": "'use strict';\n// from https://github.com/arve0/markdown-it-attrs\n// 增加多个 class 支持\nconst patternsConfig = require('./patterns.js');\nconst utils = require('./utils');\nconst defaultOptions = {\n    leftDelimiter: '{',\n    rightDelimiter: '}'\n};\n\nmodule.exports = function attributes(md, options) {\n    if (!options) {\n        options = defaultOptions;\n    }\n    utils.setOptions(options);\n\n    const patterns = patternsConfig(options);\n\n    function curlyAttrs(state) {\n        let tokens = state.tokens;\n\n        for (let i = 0; i < tokens.length; i++) {\n            for (let p = 0; p < patterns.length; p++) {\n                let pattern = patterns[p];\n                let j = null; // position of child with offset 0\n                let match = pattern.tests.every(t => {\n                    let res = test(tokens, i, t);\n                    if (res.j !== null) {\n                        j = res.j;\n                    }\n                    return res.match;\n                });\n                if (match) {\n                    pattern.transform(tokens, i, j);\n                    if (pattern.name === 'inline attributes' || pattern.name === 'inline nesting 0') {\n                        // retry, may be several inline attributes\n                        p--;\n                    }\n                }\n            }\n        }\n    }\n\n    md.core.ruler.before('linkify', 'curly_attributes', curlyAttrs);\n};\n\n/**\n * Test if t matches token stream.\n *\n * @param {array} tokens\n * @param {number} i\n * @param {object} t Test to match.\n * @return {object} { match: true|false, j: null|number }\n */\nfunction test(tokens, i, t) {\n    let res = {\n        match: false,\n        j: null // position of child\n    };\n\n    let ii = t.shift !== undefined ? i + t.shift : t.position;\n    let token = get(tokens, ii); // supports negative ii\n\n    if (token === undefined) {\n        return res;\n    }\n\n    for (let key in t) {\n        if (key === 'shift' || key === 'position') {\n            continue;\n        }\n\n        if (token[key] === undefined) {\n            return res;\n        }\n\n        if (key === 'children' && isArrayOfObjects(t.children)) {\n            if (token.children.length === 0) {\n                return res;\n            }\n            let match;\n            let childTests = t.children;\n            let children = token.children;\n            if (childTests.every(tt => tt.position !== undefined)) {\n                // positions instead of shifts, do not loop all children\n                match = childTests.every(tt => test(children, tt.position, tt).match);\n                if (match) {\n                    // we may need position of child in transform\n                    let j = last(childTests).position;\n                    res.j = j >= 0 ? j : children.length + j;\n                }\n            } else {\n                for (let j = 0; j < children.length; j++) {\n                    match = childTests.every(tt => test(children, j, tt).match);\n                    if (match) {\n                        res.j = j;\n                        // all tests true, continue with next key of pattern t\n                        break;\n                    }\n                }\n            }\n\n            if (match === false) {\n                return res;\n            }\n\n            continue;\n        }\n\n        switch (typeof t[key]) {\n            case 'boolean':\n            case 'number':\n            case 'string':\n                if (token[key] !== t[key]) {\n                    return res;\n                }\n                break;\n            case 'function':\n                if (!t[key](token[key])) {\n                    return res;\n                }\n                break;\n            case 'object':\n                if (isArrayOfFunctions(t[key])) {\n                    let r = t[key].every(tt => tt(token[key]));\n                    if (r === false) {\n                        return res;\n                    }\n                    break;\n                }\n            // fall through for objects !== arrays of functions\n            default:\n                throw new Error(\n                    `Unknown type of pattern test (key: ${key}). Test should be of type boolean, number, string, function or array of functions.`\n                );\n        }\n    }\n\n    // no tests returned false -> all tests returns true\n    res.match = true;\n    return res;\n}\n\nfunction isArrayOfObjects(arr) {\n    return Array.isArray(arr) && arr.length && arr.every(i => typeof i === 'object');\n}\n\nfunction isArrayOfFunctions(arr) {\n    return Array.isArray(arr) && arr.length && arr.every(i => typeof i === 'function');\n}\n\n/**\n * Get n item of array. Supports negative n, where -1 is last\n * element in array.\n * @param {array} arr\n * @param {number} n\n */\nfunction get(arr, n) {\n    return n >= 0 ? arr[n] : arr[arr.length + n];\n}\n\n// get last element of array, safe - returns {} if not found\nfunction last(arr) {\n    return arr.slice(-1)[0] || {};\n}\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/attrs/patterns.js",
    "content": "'use strict';\n/**\n * If a pattern matches the token stream,\n * then run transform.\n */\n\nconst utils = require('./utils.js');\n\nmodule.exports = options => {\n    const __hr = new RegExp(\n        '^ {0,3}[-*_]{3,} ?' +\n            utils.escapeRegExp(options.leftDelimiter) +\n            '[^' +\n            utils.escapeRegExp(options.rightDelimiter) +\n            ']'\n    );\n\n    return [\n        {\n            /**\n             * ```python {.cls}\n             * for i in range(10):\n             *     print(i)\n             * ```\n             */\n            name: 'fenced code blocks',\n            tests: [\n                {\n                    shift: 0,\n                    block: true,\n                    info: utils.hasDelimiters('end', options)\n                }\n            ],\n            transform: (tokens, i) => {\n                let token = tokens[i];\n                let start = token.info.lastIndexOf(options.leftDelimiter);\n                let attrs = utils.getAttrs(token.info, start, options);\n                utils.addAttrs(attrs, token);\n                token.info = utils.removeDelimiter(token.info, options);\n            }\n        },\n        {\n            /**\n             * bla [:fa-file: abc](http://){.d}\n             *\n             * differs from 'inline attributes' as it does\n             * not have a closing tag (nesting: -1)\n             */\n            name: 'fa_inline nesting',\n            tests: [\n                {\n                    shift: 0,\n                    type: 'inline',\n                    children: [\n                        {\n                            shift: -1,\n                            type: 'link_open'\n                        },\n                        {\n                            shift: 0,\n                            type: 'fa_inline'\n                        },\n                        {\n                            shift: 1,\n                            type: 'text'\n                        },\n                        {\n                            shift: 2,\n                            type: 'link_close'\n                        },\n                        {\n                            shift: 3,\n                            type: str => str === 'text' || str === 'jsx_inline',\n                            content: utils.hasDelimiters('only', options)\n                        }\n                    ]\n                }\n            ],\n            transform: (tokens, i, j) => {\n                const children = tokens[i].children;\n                for (let m = 1; m < children.length; m++) {\n                    let child = children[m];\n                    if (\n                        child &&\n                        children[m - 1] &&\n                        children[m - 1].type === 'link_open' &&\n                        child.type === 'fa_inline' &&\n                        children[m + 1] &&\n                        children[m + 1].type === 'text' &&\n                        children[m + 2] &&\n                        children[m + 2].type === 'link_close' &&\n                        children[m + 3]\n                    ) {\n                        let jsx = children[m + 3];\n                        if (utils.hasDelimiters('start', options)(jsx.content)) {\n                            // 说明是有效的 jsx\n                            let token = children.splice(m + 3, 1)[0];\n                            let attrToken = tokens[i].children[m - 1];\n                            let attrs = utils.getAttrs(token.content, 0, options);\n                            utils.addAttrs(attrs, attrToken);\n                            m--;\n                        }\n                    }\n                }\n            }\n        },\n        {\n            /**\n             * bla [xxxx](http://){.d}\n             *\n             * differs from 'inline attributes' as it does\n             * not have a closing tag (nesting: -1)\n             */\n            name: 'inline nesting link',\n            tests: [\n                {\n                    shift: 0,\n                    type: 'inline',\n                    children: [\n                        {\n                            shift: 0,\n                            type: 'link_open'\n                        },\n                        {\n                            shift: 1,\n                            type: 'text'\n                        },\n                        {\n                            shift: 2,\n                            type: 'link_close'\n                        },\n                        {\n                            shift: 3,\n                            type: str => str === 'text' || str === 'jsx_inline',\n                            content: utils.hasDelimiters('only', options)\n                        }\n                    ]\n                }\n            ],\n            transform: (tokens, i, j) => {\n                const children = tokens[i].children;\n                // console.log(children[j]);\n                for (let m = 1; m < children.length; m++) {\n                    let child = children[m];\n                    if (\n                        child &&\n                        children[m - 1] &&\n                        children[m - 1].type === 'link_open' &&\n                        child.type === 'text' &&\n                        children[m + 1] &&\n                        children[m + 1].type === 'link_close' &&\n                        children[m + 2]\n                    ) {\n                        let jsx = children[m + 2];\n                        if (utils.hasDelimiters('only', options)(jsx.content)) {\n                            // 说明是有效的 jsx\n                            let token = children.splice(m + 2, 1)[0];\n                            let attrToken = tokens[i].children[m - 1];\n                            let attrs = utils.getAttrs(token.content, 0, options);\n                            utils.addAttrs(attrs, attrToken);\n                            m--;\n                        }\n                    }\n                }\n            }\n        },\n        {\n            /**\n             * bla `click()`{.c} ![](img.png){.d}\n             *\n             * differs from 'inline attributes' as it does\n             * not have a closing tag (nesting: -1)\n             */\n            name: 'inline nesting 0',\n            tests: [\n                {\n                    shift: 0,\n                    type: 'inline',\n                    children: [\n                        {\n                            shift: -1,\n                            type: str => str === 'image' || str === 'code_inline'\n                        },\n                        {\n                            shift: 0,\n                            type: str => str === 'text' || str === 'jsx_inline',\n                            content: utils.hasDelimiters('start', options)\n                        }\n                    ]\n                }\n            ],\n            transform: (tokens, i, j) => {\n                let token = tokens[i].children[j];\n                let endChar = token.content.indexOf(options.rightDelimiter);\n                let attrToken = tokens[i].children[j - 1];\n                let attrs = utils.getAttrs(token.content, 0, options);\n                utils.addAttrs(attrs, attrToken);\n                if (token.content.length === endChar + options.rightDelimiter.length) {\n                    tokens[i].children.splice(j, 1);\n                } else {\n                    token.content = token.content.slice(endChar + options.rightDelimiter.length);\n                }\n            }\n        },\n        {\n            /**\n             * | h1 |\n             * | -- |\n             * | c1 |\n             * {.c}\n             */\n            name: 'tables',\n            tests: [\n                {\n                    // let this token be i, such that for-loop continues at\n                    // next token after tokens.splice\n                    shift: 0,\n                    type: 'table_close'\n                },\n                {\n                    shift: 1,\n                    type: 'paragraph_open'\n                },\n                {\n                    shift: 2,\n                    type: 'inline',\n                    content: utils.hasDelimiters('only', options)\n                }\n            ],\n            transform: (tokens, i) => {\n                let token = tokens[i + 2];\n                let tableOpen = utils.getMatchingOpeningToken(tokens, i);\n                let attrs = utils.getAttrs(token.content, 0, options);\n                // add attributes\n                utils.addAttrs(attrs, tableOpen);\n                // remove <p>{.c}</p>\n                tokens.splice(i + 1, 3);\n            }\n        },\n        {\n            /**\n             * *emphasis*{.with attrs=1}\n             */\n            name: 'inline attributes',\n            tests: [\n                {\n                    shift: 0,\n                    type: 'inline',\n                    children: [\n                        {\n                            shift: -1,\n                            nesting: -1 // closing inline tag, </em>{.a}\n                        },\n                        {\n                            shift: 0,\n                            type: 'text',\n                            content: utils.hasDelimiters('start', options)\n                        }\n                    ]\n                }\n            ],\n            transform: (tokens, i, j) => {\n                let token = tokens[i].children[j];\n                let content = token.content;\n                let attrs = utils.getAttrs(content, 0, options);\n                let openingToken = utils.getMatchingOpeningToken(tokens[i].children, j - 1);\n                utils.addAttrs(attrs, openingToken);\n                token.content = content.slice(content.indexOf(options.rightDelimiter) + options.rightDelimiter.length);\n            }\n        },\n        {\n            /**\n             * - item\n             * {.a}\n             */\n            name: 'list softbreak',\n            tests: [\n                {\n                    shift: -2,\n                    type: 'list_item_open'\n                },\n                {\n                    shift: 0,\n                    type: 'inline',\n                    children: [\n                        {\n                            position: -2,\n                            type: 'softbreak'\n                        },\n                        {\n                            position: -1,\n                            content: utils.hasDelimiters('only', options)\n                        }\n                    ]\n                }\n            ],\n            transform: (tokens, i, j) => {\n                let token = tokens[i].children[j];\n                let content = token.content;\n                let attrs = utils.getAttrs(content, 0, options);\n                let ii = i - 2;\n                while (\n                    tokens[ii - 1] &&\n                    tokens[ii - 1].type !== 'ordered_list_open' &&\n                    tokens[ii - 1].type !== 'bullet_list_open'\n                ) {\n                    ii--;\n                }\n                utils.addAttrs(attrs, tokens[ii - 1]);\n                tokens[i].children = tokens[i].children.slice(0, -2);\n            }\n        },\n        {\n            /**\n             * - nested list\n             *   - with double \\n\n             *   {.a} <-- apply to nested ul\n             *\n             * {.b} <-- apply to root <ul>\n             */\n            name: 'list double softbreak',\n            tests: [\n                {\n                    // let this token be i = 0 so that we can erase\n                    // the <p>{.a}</p> tokens below\n                    shift: 0,\n                    type: str => str === 'bullet_list_close' || str === 'ordered_list_close'\n                },\n                {\n                    shift: 1,\n                    type: 'paragraph_open'\n                },\n                {\n                    shift: 2,\n                    type: 'inline',\n                    content: utils.hasDelimiters('only', options),\n                    children: arr => arr.length === 1\n                },\n                {\n                    shift: 3,\n                    type: 'paragraph_close'\n                }\n            ],\n            transform: (tokens, i) => {\n                let token = tokens[i + 2];\n                let content = token.content;\n                let attrs = utils.getAttrs(content, 0, options);\n                let openingToken = utils.getMatchingOpeningToken(tokens, i);\n                utils.addAttrs(attrs, openingToken);\n                tokens.splice(i + 1, 3);\n            }\n        },\n        {\n            /**\n             * - end of {.list-item}\n             */\n            name: 'list item end',\n            tests: [\n                {\n                    shift: -2,\n                    type: 'list_item_open'\n                },\n                {\n                    shift: 0,\n                    type: 'inline',\n                    children: [\n                        {\n                            position: -1,\n                            content: utils.hasDelimiters('end', options)\n                        }\n                    ]\n                }\n            ],\n            transform: (tokens, i, j) => {\n                let token = tokens[i].children[j];\n                let content = token.content;\n                let attrs = utils.getAttrs(content, content.lastIndexOf(options.leftDelimiter), options);\n                utils.addAttrs(attrs, tokens[i - 2]);\n                let trimmed = content.slice(0, content.lastIndexOf(options.leftDelimiter));\n                token.content = last(trimmed) !== ' ' ? trimmed : trimmed.slice(0, -1);\n            }\n        },\n        {\n            /**\n             * something with softbreak\n             * {.cls}\n             */\n            name: '\\n{.a} softbreak then curly in start',\n            tests: [\n                {\n                    shift: 0,\n                    type: 'inline',\n                    children: [\n                        {\n                            position: -2,\n                            type: 'softbreak'\n                        },\n                        {\n                            position: -1,\n                            type: str => str === 'text' || str === 'jsx_inline',\n                            content: utils.hasDelimiters('only', options)\n                        }\n                    ]\n                }\n            ],\n            transform: (tokens, i, j) => {\n                let token = tokens[i].children[j];\n                let attrs = utils.getAttrs(token.content, 0, options);\n                // find last closing tag\n                let ii = i + 1;\n                while (tokens[ii + 1] && tokens[ii + 1].nesting === -1) {\n                    ii++;\n                }\n                let openingToken = utils.getMatchingOpeningToken(tokens, ii);\n                utils.addAttrs(attrs, openingToken);\n                tokens[i].children = tokens[i].children.slice(0, -2);\n            }\n        },\n        {\n            /**\n             * horizontal rule --- {#id}\n             */\n            name: 'horizontal rule',\n            tests: [\n                {\n                    shift: 0,\n                    type: 'paragraph_open'\n                },\n                {\n                    shift: 1,\n                    type: 'inline',\n                    children: arr => arr.length === 1,\n                    content: str => str.match(__hr) !== null\n                },\n                {\n                    shift: 2,\n                    type: 'paragraph_close'\n                }\n            ],\n            transform: (tokens, i) => {\n                let token = tokens[i];\n                token.type = 'hr';\n                token.tag = 'hr';\n                token.nesting = 0;\n                let content = tokens[i + 1].content;\n                let start = content.lastIndexOf(options.leftDelimiter);\n                token.attrs = utils.getAttrs(content, start, options);\n                token.markup = content;\n                tokens.splice(i + 1, 2);\n            }\n        },\n        {\n            /**\n             * end of {.block}\n             */\n            name: 'end of block',\n            tests: [\n                {\n                    shift: 0,\n                    type: 'inline',\n                    children: [\n                        {\n                            position: -1,\n                            content: utils.hasDelimiters('end', options),\n                            type: t => t !== 'code_inline'\n                        }\n                    ]\n                }\n            ],\n            transform: (tokens, i, j) => {\n                let token = tokens[i].children[j];\n                let content = token.content;\n                let attrs = utils.getAttrs(content, content.lastIndexOf(options.leftDelimiter), options);\n                let ii = i + 1;\n                while (tokens[ii + 1] && tokens[ii + 1].nesting === -1) {\n                    ii++;\n                }\n                let openingToken = utils.getMatchingOpeningToken(tokens, ii);\n                utils.addAttrs(attrs, openingToken);\n                let trimmed = content.slice(0, content.lastIndexOf(options.leftDelimiter));\n                token.content = last(trimmed) !== ' ' ? trimmed : trimmed.slice(0, -1);\n            }\n        }\n    ];\n};\n\n// get last element of array or string\nfunction last(arr) {\n    return arr.slice(-1)[0];\n}\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/attrs/utils.js",
    "content": "'use strict';\nexports.findAttrs = (attrArray, name, getValueOnly = true) => {\n    let rs = (attrArray || []).find(([key, value]) => {\n        if (key === name) {\n            return true;\n        }\n        return false;\n    });\n    if (getValueOnly) {\n        return rs[1];\n    }\n    return rs;\n};\n/**\n * parse {.class #id key=val} strings\n * @param {string} str: string to parse\n * @param {int} start: where to start parsing (including {)\n * @returns {2d array}: [['key', 'val'], ['class', 'red']]\n */\nexports.getAttrs = function(str, start, options) {\n    // not tab, line feed, form feed, space, solidus, greater than sign, quotation mark, apostrophe and equals sign\n    const allowedKeyChars = /[^\\t\\n\\f />\"'=]/;\n    const pairSeparator = ' ';\n    const keySeparator = '=';\n    const classChar = '.';\n    const idChar = '#';\n\n    const attrs = [];\n    let key = '';\n    let value = '';\n    let parsingKey = true;\n    let valueInsideQuotes = false;\n\n    // read inside {}\n    // start + left delimiter length to avoid beginning {\n    // breaks when } is found or end of string\n    for (let i = start + options.leftDelimiter.length; i < str.length; i++) {\n        if (str.slice(i, i + options.rightDelimiter.length) === options.rightDelimiter) {\n            if (key !== '') {\n                attrs.push([key, value]);\n            }\n            break;\n        }\n        let char_ = str.charAt(i);\n\n        // switch to reading value if equal sign\n        if (char_ === keySeparator && parsingKey) {\n            parsingKey = false;\n            continue;\n        }\n\n        // {.class} {..css-module}\n        if (char_ === classChar && key === '') {\n            // console.log(str)\n            if (str.charAt(i + 1) === classChar) {\n                key = 'css-module';\n                i += 1;\n            } else {\n                key = 'class';\n            }\n            parsingKey = false;\n            continue;\n        }\n\n        // {#id}\n        if (char_ === idChar && key === '') {\n            key = 'id';\n            parsingKey = false;\n            continue;\n        }\n\n        // {value=\"inside quotes\"}\n        if (char_ === '\"' && value === '') {\n            valueInsideQuotes = true;\n            continue;\n        }\n        if (char_ === '\"' && valueInsideQuotes) {\n            valueInsideQuotes = false;\n            continue;\n        }\n\n        // read next key/value pair\n        if (char_ === pairSeparator && !valueInsideQuotes) {\n            if (key === '') {\n                // beginning or ending space: { .red } vs {.red}\n                continue;\n            }\n            attrs.push([key, value]);\n            key = '';\n            value = '';\n            parsingKey = true;\n            continue;\n        }\n\n        // continue if character not allowed\n        if (parsingKey && char_.search(allowedKeyChars) === -1) {\n            continue;\n        }\n\n        // no other conditions met; append to key/value\n        if (parsingKey) {\n            key += char_;\n            continue;\n        }\n        value += char_;\n    }\n    return attrs;\n};\n\n/**\n * add attributes from [['key', 'val']] list\n * @param {array} attrs: [['key', 'val']]\n * @param {token} token: which token to add attributes\n * @returns token\n */\nexports.addAttrs = function(attrs, token) {\n    for (let j = 0, l = attrs.length; j < l; ++j) {\n        let key = attrs[j][0];\n        // 多个. 支持\n        if (key === 'class') {\n            token.attrJoin('class', attrs[j][1].split('.').join(' '));\n        } else if (key === 'css-module') {\n            token.attrJoin('css-module', attrs[j][1].split('.').join(' '));\n        } else {\n            token.attrPush(attrs[j]);\n        }\n    }\n    return token;\n};\n// 增加获取 attr string 方法\nexports.getAttrsString = function(attrs, fn) {\n    const defFN = (key, value) => `${key}=\"${value}\"`;\n    const cb =\n        typeof fn === 'function'\n            ? (key, value) => {\n                  const r = fn(key, value);\n                  if (r) {\n                      return r;\n                  } else {\n                      return defFN(key, value);\n                  }\n              }\n            : (key, value) => `${key}=\"${value}\"`;\n    const str = [];\n    for (let j = 0, l = attrs.length; j < l; ++j) {\n        let key = attrs[j][0];\n        // 多个. 支持\n        if (key === 'class' || key === 'css-module') {\n            str.push(cb(key, attrs[j][1].split('.').join(' ')));\n        } else {\n            str.push(cb(key, attrs[j][1]));\n        }\n    }\n    return str.join(' ');\n};\n\n/**\n * Does string have properly formatted curly?\n *\n * start: '{.a} asdf'\n * middle: 'a{.b}c'\n * end: 'asdf {.a}'\n * only: '{.a}'\n *\n * @param {string} where to expect {} curly. start, middle, end or only.\n * @return {function(string)} Function which testes if string has curly.\n */\nexports.hasDelimiters = function(where, options) {\n    if (!where) {\n        throw new Error('Parameter `where` not passed. Should be \"start\", \"middle\", \"end\" or \"only\".');\n    }\n\n    /**\n     * @param {string} str\n     * @return {boolean}\n     */\n    return function(str) {\n        // we need minimum three chars, for example {b}\n        let minCurlyLength = options.leftDelimiter.length + 1 + options.rightDelimiter.length;\n        if (!str || typeof str !== 'string' || str.length < minCurlyLength) {\n            return false;\n        }\n\n        function validCurlyLength(curly) {\n            let isClass = curly.charAt(options.leftDelimiter.length) === '.';\n            let isId = curly.charAt(options.leftDelimiter.length) === '#';\n            return isClass || isId ? curly.length >= minCurlyLength + 1 : curly.length >= minCurlyLength;\n        }\n\n        let start, end, slice, nextChar;\n        let rightDelimiterMinimumShift = minCurlyLength - options.rightDelimiter.length;\n        switch (where) {\n            case 'start':\n                // first char should be {, } found in char 2 or more\n                slice = str.slice(0, options.leftDelimiter.length);\n                start = slice === options.leftDelimiter ? 0 : -1;\n                end = start === -1 ? -1 : str.indexOf(options.rightDelimiter, rightDelimiterMinimumShift);\n                // check if next character is not one of the delimiters\n                nextChar = str.charAt(end + options.rightDelimiter.length);\n                if (nextChar && options.rightDelimiter.indexOf(nextChar) !== -1) {\n                    end = -1;\n                }\n                break;\n\n            case 'end':\n                // last char should be }\n                start = str.lastIndexOf(options.leftDelimiter);\n                end = start === -1 ? -1 : str.indexOf(options.rightDelimiter, start + rightDelimiterMinimumShift);\n                end = end === str.length - options.rightDelimiter.length ? end : -1;\n                break;\n\n            case 'only':\n                // '{.a}'\n                slice = str.slice(0, options.leftDelimiter.length);\n                start = slice === options.leftDelimiter ? 0 : -1;\n                slice = str.slice(str.length - options.rightDelimiter.length);\n                end = slice === options.rightDelimiter ? str.length - options.rightDelimiter.length : -1;\n                break;\n        }\n\n        return (\n            start !== -1 && end !== -1 && validCurlyLength(str.substring(start, end + options.rightDelimiter.length))\n        );\n    };\n};\nlet latestOptions = {};\nexports.setOptions = opts => {\n    latestOptions = opts;\n};\nexports.getOptions = () => {\n    return latestOptions;\n};\n/**\n * Removes last curly from string.\n */\nexports.removeDelimiter = function(str, options) {\n    const start = escapeRegExp(options.leftDelimiter);\n    const end = escapeRegExp(options.rightDelimiter);\n\n    let curly = new RegExp('[ \\\\n]?' + start + '[^' + start + end + ']+' + end + '$');\n    let pos = str.search(curly);\n\n    return pos !== -1 ? str.slice(0, pos) : str;\n};\n\n/**\n * Escapes special characters in string s such that the string\n * can be used in `new RegExp`. For example \"[\" becomes \"\\\\[\".\n *\n * @param {string} s Regex string.\n * @return {string} Escaped string.\n */\nfunction escapeRegExp(s) {\n    return s.replace(/[-/\\\\^$*+?.()|[\\]{}]/g, '\\\\$&');\n}\nexports.escapeRegExp = escapeRegExp;\n\n/**\n * find corresponding opening block\n */\nexports.getMatchingOpeningToken = function(tokens, i) {\n    if (tokens[i].type === 'softbreak') {\n        return false;\n    }\n    // non closing blocks, example img\n    if (tokens[i].nesting === 0) {\n        return tokens[i];\n    }\n\n    // inline tokens changes level on same token\n    // that have .nesting +- 1\n    let level = tokens[i].block ? tokens[i].level : tokens[i].level + 1; // adjust for inline tokens\n\n    let type = tokens[i].type.replace('_close', '_open');\n\n    for (; i >= 0; --i) {\n        if (tokens[i].type === type && tokens[i].level === level) {\n            return tokens[i];\n        }\n    }\n};\n\n/**\n * from https://github.com/markdown-it/markdown-it/blob/master/lib/common/utils.js\n */\nlet HTML_ESCAPE_TEST_RE = /[&<>\"]/;\nlet HTML_ESCAPE_REPLACE_RE = /[&<>\"]/g;\nlet HTML_REPLACEMENTS = {\n    '&': '&amp;',\n    '<': '&lt;',\n    '>': '&gt;',\n    '\"': '&quot;'\n};\n\nfunction replaceUnsafeChar(ch) {\n    return HTML_REPLACEMENTS[ch];\n}\n\nexports.escapeHtml = function(str) {\n    if (HTML_ESCAPE_TEST_RE.test(str)) {\n        return str.replace(HTML_ESCAPE_REPLACE_RE, replaceUnsafeChar);\n    }\n    return str;\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/cite.js",
    "content": "module.exports = function ins_plugin(md) {\n    // https://github.com/markdown-it/markdown-it-mark/blob/master/index.js\n    function tokenize(state, silent) {\n        var i,\n            scanned,\n            token,\n            len,\n            ch,\n            start = state.pos,\n            marker = state.src.charCodeAt(start);\n\n        if (silent) {\n            return false;\n        }\n\n        if (marker !== 0x3d /* = */) {\n            return false;\n        }\n\n        scanned = state.scanDelims(state.pos, true);\n        len = scanned.length;\n        ch = String.fromCharCode(marker);\n\n        if (len < 2) {\n            return false;\n        }\n\n        if (len % 2) {\n            token = state.push('text', '', 0);\n            token.content = ch;\n            len--;\n        }\n\n        for (i = 0; i < len; i += 2) {\n            token = state.push('text', '', 0);\n            token.content = ch + ch;\n\n            state.delimiters.push({\n                marker: marker,\n                jump: i,\n                token: state.tokens.length - 1,\n                level: state.level,\n                end: -1,\n                open: scanned.can_open,\n                close: scanned.can_close\n            });\n        }\n\n        state.pos += scanned.length;\n\n        return true;\n    }\n\n    // Walk through delimiter list and replace text tokens with tags\n    //\n    function postProcess(state) {\n        var i,\n            j,\n            startDelim,\n            endDelim,\n            token,\n            loneMarkers = [],\n            delimiters = state.delimiters,\n            max = state.delimiters.length;\n\n        for (i = 0; i < max; i++) {\n            startDelim = delimiters[i];\n\n            if (startDelim.marker !== 0x3d /* = */) {\n                continue;\n            }\n\n            if (startDelim.end === -1) {\n                continue;\n            }\n\n            endDelim = delimiters[startDelim.end];\n\n            token = state.tokens[startDelim.token];\n            token.type = 'cite_open';\n            token.tag = 'cite';\n            token.nesting = 1;\n            token.markup = '==';\n            token.content = '';\n\n            token = state.tokens[endDelim.token];\n            token.type = 'cite_close';\n            token.tag = 'cite';\n            token.nesting = -1;\n            token.markup = '==';\n            token.content = '';\n\n            if (state.tokens[endDelim.token - 1].type === 'text' && state.tokens[endDelim.token - 1].content === '=') {\n                loneMarkers.push(endDelim.token - 1);\n            }\n        }\n\n        // If a marker sequence has an odd number of characters, it's splitted\n        // like this: `~~~~~` -> `~` + `~~` + `~~`, leaving one marker at the\n        // start of the sequence.\n        //\n        // So, we have to move all those markers after subsequent s_close tags.\n        //\n        while (loneMarkers.length) {\n            i = loneMarkers.pop();\n            j = i + 1;\n\n            while (j < state.tokens.length && state.tokens[j].type === 'cite_close') {\n                j++;\n            }\n\n            j--;\n\n            if (i !== j) {\n                token = state.tokens[j];\n                state.tokens[j] = state.tokens[i];\n                state.tokens[i] = token;\n            }\n        }\n    }\n\n    md.inline.ruler.before('emphasis', 'cite', tokenize);\n    md.inline.ruler2.before('emphasis', 'cite', postProcess);\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/container.js",
    "content": "// 参考 markdown-it-container\nmodule.exports = function container_plugin(md, name, options) {\n    function validateDefault(params) {\n        return params.trim().split(' ', 2)[0] === name;\n    }\n\n    function renderDefault(tokens, idx, _options, env, slf) {\n        // add a class to the opening tag\n        if (tokens[idx].nesting === 1) {\n            tokens[idx].attrJoin('class', name);\n        }\n\n        return slf.renderToken(tokens, idx, _options, env, slf);\n    }\n    function emptyFn(t) {\n        return t;\n    }\n\n    options = options || {};\n\n    var min_markers = 3,\n        marker_str = options.marker || ':',\n        marker_char = marker_str.charCodeAt(0),\n        marker_len = marker_str.length,\n        validate = options.validate || validateDefault,\n        handler = options.handler || emptyFn,\n        render = options.render || renderDefault;\n\n    function container(state, startLine, endLine, silent) {\n        var pos,\n            nextLine,\n            marker_count,\n            markup,\n            params,\n            token,\n            old_parent,\n            old_line_max,\n            auto_closed = false,\n            start = state.bMarks[startLine] + state.tShift[startLine],\n            max = state.eMarks[startLine];\n\n        // Check out the first character quickly,\n        // this should filter out most of non-containers\n        //\n        if (marker_char !== state.src.charCodeAt(start)) {\n            return false;\n        }\n        // Check out the rest of the marker string\n        //\n        for (pos = start + 1; pos <= max; pos++) {\n            if (marker_str[(pos - start) % marker_len] !== state.src[pos]) {\n                break;\n            }\n        }\n\n        marker_count = Math.floor((pos - start) / marker_len);\n        if (marker_count < min_markers) {\n            return false;\n        }\n        pos -= (pos - start) % marker_len;\n\n        markup = state.src.slice(start, pos);\n        params = state.src.slice(pos, max);\n        if (!validate(params)) {\n            return false;\n        }\n\n        // Since start is found, we can report success here in validation mode\n        //\n        if (silent) {\n            return true;\n        }\n\n        // Search for the end of the block\n        //\n        nextLine = startLine;\n\n        for (;;) {\n            nextLine++;\n            if (nextLine >= endLine) {\n                // unclosed block should be autoclosed by end of document.\n                // also block seems to be autoclosed by end of parent\n                break;\n            }\n\n            start = state.bMarks[nextLine] + state.tShift[nextLine];\n            max = state.eMarks[nextLine];\n\n            if (start < max && state.sCount[nextLine] < state.blkIndent) {\n                // non-empty line with negative indent should stop the list:\n                // - ```\n                //  test\n                break;\n            }\n\n            if (marker_char !== state.src.charCodeAt(start)) {\n                continue;\n            }\n\n            if (state.sCount[nextLine] - state.blkIndent >= 4) {\n                // closing fence should be indented less than 4 spaces\n                continue;\n            }\n\n            for (pos = start + 1; pos <= max; pos++) {\n                if (marker_str[(pos - start) % marker_len] !== state.src[pos]) {\n                    break;\n                }\n            }\n\n            // closing code fence must be at least as long as the opening one\n            if (Math.floor((pos - start) / marker_len) < marker_count) {\n                continue;\n            }\n\n            // make sure tail has spaces only\n            pos -= (pos - start) % marker_len;\n            pos = state.skipSpaces(pos);\n\n            if (pos < max) {\n                continue;\n            }\n\n            // found!\n            auto_closed = true;\n            break;\n        }\n\n        old_parent = state.parentType;\n        old_line_max = state.lineMax;\n        state.parentType = 'container';\n\n        // this will prevent lazy continuations from ever going past our end marker\n        state.lineMax = nextLine;\n\n        token = state.push('container_' + name + '_open', 'div', 1);\n        token.markup = markup;\n        token.block = true;\n        token.info = params;\n        token.map = [startLine, nextLine];\n\n        // 修正\n        let len = state.tokens.length - startLine;\n\n        state.md.block.tokenize(state, startLine + 1, nextLine);\n\n        token = state.push('container_' + name + '_close', 'div', -1);\n        token.markup = state.src.slice(start, pos);\n        token.block = true;\n        token.info = params;\n\n        // 这里测试下\n        handler(state, params, startLine + len - 1, len);\n\n        state.parentType = old_parent;\n        state.lineMax = old_line_max;\n        state.line = nextLine + (auto_closed ? 1 : 0);\n        return true;\n    }\n\n    md.block.ruler.before('fence', 'container_' + name, container, {\n        alt: ['paragraph', 'reference', 'blockquote', 'list']\n    });\n    md.renderer.rules['container_' + name + '_open'] = render;\n    md.renderer.rules['container_' + name + '_close'] = render;\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/containers/blink.js",
    "content": "const name = 'blink';\n\nmodule.exports = {\n    validate(params) {\n        return params.trim().match(new RegExp('^' + name + '\\\\s*(.*)$'));\n    },\n    handler(state, opts, start) {\n        function getOpenToken(tag, level) {\n            const token = new state.Token('container_' + name + '_' + tag + '_open', tag, 1);\n            token.block = true;\n            token.level = 1 + level;\n            return token;\n        }\n        function getCloseToken(tag, level) {\n            const token = new state.Token('container_' + name + '_' + tag + '_close', tag, -1);\n            token.block = true;\n            token.level = 1 + level;\n            return token;\n        }\n        // tokens\n        const tokens = state.tokens;\n\n        let open = false;\n        let done = 0;\n        for (let i = start; i < tokens.length; i++) {\n            const token = tokens[i];\n            if (token.type === 'container_' + name + '_open') {\n                // 在 open 后面插入\n                tokens.splice(i + 1, 0, getOpenToken('li', token.level), getOpenToken('a', token.level + 1));\n                open = true;\n                i = i + 2;\n            } else if (token.type === 'container_' + name + '_close') {\n                // 在 close 之前插入\n                tokens.splice(i, 0, getCloseToken('a', token.level + 1), getCloseToken('li', token.level));\n                open = false;\n                i = i + 2;\n            } else if (open && 'hr' === token.type && done === 0) {\n                // 第一层的 Hr 需要替换\n                tokens.splice(\n                    i,\n                    1,\n                    getCloseToken('a', token.level),\n                    getCloseToken('li', token.level - 1),\n                    getOpenToken('li', token.level - 1),\n                    getOpenToken('a', token.level)\n                );\n                i = i + 3;\n            } else if (open) {\n                if (token.type === 'paragraph_close' || token.type === 'paragraph_open') {\n                    tokens.splice(i, 1);\n                    i--;\n                    continue;\n                }\n                // 加深一层，因为外面多套了一层div\n                token.level = token.level + 2;\n                // 保证hr 是最贴近 container 的一层\n                if (/_open$/.test(token.type)) {\n                    done++;\n                } else if (/_close$/.test(token.type)) {\n                    done--;\n                }\n            }\n        }\n        return state;\n    },\n    render(tokens, idx) {\n        const token = tokens[idx];\n\n        if (token.nesting === 1) {\n            const cmIndex = token.attrIndex('css-module');\n            let clsIndex = token.attrIndex('class');\n            let attrs = token.attrs || [];\n\n            if (clsIndex >= 0) {\n                attrs[clsIndex][1] +=\n                    cmIndex >= 0 ? ` flexblock blink border ${attrs[cmIndex][1]}` : ` flexblock blink border`;\n            } else {\n                attrs.push([\n                    'class',\n                    cmIndex >= 0 ? `flexblock blink border ${attrs[cmIndex][1]}` : `flexblock blink border`\n                ]);\n            }\n\n            attrs = attrs.map(([key, value]) => {\n                return `${key}=\"${value}\"`;\n            });\n            // opening tag\n            return `<ul ${attrs.join(' ')}>\\n`;\n        } else {\n            // closing tag\n            return '</ul>\\n';\n        }\n    }\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/containers/card.js",
    "content": "const utils = require('../attrs/utils');\nconst attrOptions = utils.getOptions();\nconst name = 'card';\n\nmodule.exports = {\n    validate(params) {\n        return params.trim().match(new RegExp('^' + name + '(-\\\\d+)?\\\\s*(.*)$'));\n    },\n    handler(state, opts, start) {\n        function getOpenToken(tag, level) {\n            const token = new state.Token('container_' + name + '_' + tag + '_open', tag, 1);\n            token.block = true;\n            token.level = 1 + level;\n            return token;\n        }\n        function getCloseToken(tag, level) {\n            const token = new state.Token('container_' + name + '_' + tag + '_close', tag, -1);\n            token.block = true;\n            token.level = 1 + level;\n            return token;\n        }\n        function hasImg(tokens) {\n            if (\n                tokens.length === 3 &&\n                tokens[0].type === 'paragraph_open' &&\n                tokens[2].type === 'paragraph_close' &&\n                /^\\!\\[/.test(tokens[1].content)\n            ) {\n                return [\n                    getOpenToken('figure', tokens[0].level - 1),\n                    tokens[1],\n                    getCloseToken('figure', tokens[2].level - 1)\n                ];\n            }\n            return false;\n        }\n\n        opts = opts.split(/card(?:-\\d+)?\\s+/).splice(1)[0];\n        let attrs = [];\n        let cardClass = '';\n        if (utils.hasDelimiters('only', attrOptions)(opts, attrOptions)) {\n            attrs = utils.getAttrs(opts, 0, attrOptions);\n            cardClass = utils.findAttrs(attrs, 'class');\n        }\n        // tokens\n        const tokens = state.tokens;\n        let hrIdx = 0;\n        for (let i = start + 1; i < tokens.length - 1; i++) {\n            // 第一步，查找hr\n            let token = tokens[i];\n            if (token.type === 'hr') {\n                hrIdx = i;\n                break;\n            }\n        }\n\n        // 第二步：拆分\n        let part1 = tokens.slice(start + 1, hrIdx);\n        let part2 = tokens.slice(hrIdx + 1, tokens.length - 1);\n        // console.log(part1, part2);\n        // 第三步：查找哪part有img\n        let imgs = hasImg(part1);\n        if (imgs) {\n            // 第一部分有图片\n            tokens.splice(start + 1, 3, ...imgs);\n            if (cardClass.indexOf('quote') === -1) {\n                let level = tokens[start].level;\n                const divToken = getOpenToken('div', level - 1);\n                divToken.attrPush(['class', 'flex-content']);\n                tokens.splice(hrIdx, 1, divToken);\n                tokens.splice(tokens.length - 1, 0, getCloseToken('div', level - 1));\n            } else {\n                tokens.splice(hrIdx, 1);\n            }\n\n            // console.log(tokens);\n        } else {\n            imgs = hasImg(part2);\n            if (imgs) {\n                // 第二部分有图片\n                if (cardClass.indexOf('quote') === -1) {\n                    let level = tokens[start].level;\n                    const divToken = getOpenToken('div', level - 1);\n                    divToken.attrPush(['class', 'flex-content']);\n                    tokens.splice(start + 1, 0, divToken);\n                    tokens.splice(hrIdx + 1, 4, getCloseToken('div', level - 1), ...imgs);\n                } else {\n                    tokens.splice(hrIdx, 3, ...imgs);\n                }\n            }\n        }\n        // console.log(tokens.slice(start - 1));\n    },\n    render(tokens, idx) {\n        const token = tokens[idx];\n        if (token.nesting === 1) {\n            let info = token.info.split(' ').shift();\n\n            const cmIndex = token.attrIndex('css-module');\n            let clsIndex = token.attrIndex('class');\n            let attrs = token.attrs || [];\n            if (info === 'card') {\n                info = 'card-50';\n            }\n            if (clsIndex >= 0) {\n                attrs[clsIndex][1] += cmIndex >= 0 ? ` ${info} ${attrs[cmIndex][1]}` : ` ${info}`;\n            } else {\n                attrs.push(['class', cmIndex >= 0 ? ` ${info} ${attrs[cmIndex][1]}` : ` ${info}`]);\n            }\n\n            attrs = attrs.map(([key, value]) => {\n                if (key === 'class') {\n                    // 处理掉 quote 存在 bg-wihte情况，和多个 bg-*情况\n\n                    if (!~value.indexOf('bg') && value.indexOf('quote') === -1) {\n                        value += ' bg-white';\n                    }\n                }\n                return `${key}=\"${value}\"`;\n            });\n            // opening tag\n            return `<div ${attrs.join(' ')}>\\n`;\n        } else {\n            // closing tag\n            return '</div>\\n';\n        }\n    }\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/containers/column.js",
    "content": "const name = 'column';\n\nmodule.exports = {\n    validate(params) {\n        return params.trim().match(/^column\\s*(.*)$/);\n    },\n    handler(state, opts) {\n        function getOpenToken(level) {\n            const token = new state.Token('container_' + name + '_item_open', 'div', 1);\n            token.block = true;\n            token.level = 1 + level;\n            token.attrs = [['class', name]];\n            return token;\n        }\n        function getCloseToken(level) {\n            const token = new state.Token('container_' + name + '_item_close', 'div', -1);\n            token.block = true;\n            token.level = 1 + level;\n            return token;\n        }\n        // tokens\n        const tokens = state.tokens;\n\n        let open = false;\n        let done = 0;\n        for (let i = 0; i < tokens.length; i++) {\n            const token = tokens[i];\n            if (token.type === 'container_' + name + '_open') {\n                // 在 open 后面插入\n                tokens.splice(i + 1, 0, getOpenToken(token.level));\n                open = true;\n                i++;\n            } else if (token.type === 'container_' + name + '_close') {\n                // 在 close 之前插入\n                tokens.splice(i, 0, getCloseToken(token.level));\n                open = false;\n                i++;\n            } else if (open && 'hr' === token.type && done === 0) {\n                // 第一层的 Hr 需要替换\n                tokens.splice(i, 1, getCloseToken(token.level - 1), getOpenToken(token.level - 1));\n                i++;\n            } else if (open) {\n                // 加深一层，因为外面多套了一层div\n                token.level = token.level + 1;\n                // 保证hr 是最贴近 container 的一层\n                if (/_open$/.test(token.type)) {\n                    done++;\n                } else if (/_close$/.test(token.type)) {\n                    done--;\n                }\n            }\n        }\n        return state;\n    },\n    render(tokens, idx) {\n        const token = tokens[idx];\n\n        if (token.nesting === 1) {\n            const cmIndex = token.attrIndex('css-module');\n            let clsIndex = token.attrIndex('class');\n            let attrs = token.attrs || [];\n\n            if (clsIndex >= 0) {\n                attrs[clsIndex][1] += cmIndex >= 0 ? ` grid ${attrs[cmIndex][1]}` : ' grid';\n            } else {\n                attrs.push(['class', cmIndex >= 0 ? `grid ${attrs[cmIndex][1]}` : 'grid']);\n            }\n\n            attrs = attrs.map(([key, value]) => {\n                return `${key}=\"${value}\"`;\n            });\n            // opening tag\n            return `<div ${attrs.join(' ')}>\\n`;\n        } else {\n            // closing tag\n            return '</div>\\n';\n        }\n    }\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/containers/cta.js",
    "content": "const name = 'cta';\nmodule.exports = {\n    validate(params) {\n        return params.trim().match(new RegExp('^' + name + '\\\\s*(.*)$'));\n    },\n    handler(state, opts) {\n        function getOpenToken(tag, level) {\n            const token = new state.Token('container_' + name + '_' + tag + '_open', tag, 1);\n            token.block = true;\n            token.level = 1 + level;\n            return token;\n        }\n        function getCloseToken(tag, level) {\n            const token = new state.Token('container_' + name + '_' + tag + '_close', tag, -1);\n            token.block = true;\n            token.level = 1 + level;\n            return token;\n        }\n        // tokens\n        const tokens = state.tokens;\n\n        let open = false;\n        let done = 0;\n        let count = 1;\n        let level = 1;\n        let imgStart = 0,\n            imgEnd = 0;\n        let ctxStart = 0,\n            ctxEnd = 0;\n\n        let img = [],\n            context = [];\n        for (let i = 0; i < tokens.length; i++) {\n            let token = tokens[i];\n            if (token.type === 'container_' + name + '_open') {\n                // 在 open 后面插入\n                open = true;\n                level = token.level + 1;\n            } else if (token.type === 'container_' + name + '_close') {\n                // 在 close 之前插入\n                open = false;\n            } else if (open && 'hr' === token.type && done === 0) {\n                tokens.splice(i, 1);\n                i--;\n                count++;\n            } else if (open) {\n                // 加深一层，因为外面多套了一层div\n                // token.level = token.level + 2;\n                // 保证hr 是最贴近 container 的一层\n                if (/_open$/.test(token.type)) {\n                    done++;\n                } else if (/_close$/.test(token.type)) {\n                    done--;\n                }\n                if (count === 1) {\n                    if (!imgStart) {\n                        imgStart = i;\n                    } else {\n                        imgEnd = i;\n                    }\n                    img.push(token);\n                } else {\n                    if (!ctxStart) {\n                        ctxStart = i;\n                    } else {\n                        ctxEnd = i;\n                    }\n                    token.level = token.level + 1;\n                    context.push(token);\n                }\n            }\n        }\n        // 分组完成\n        const divNumber = getOpenToken('div', level - 1);\n        divNumber.attrPush(['class', 'number']);\n        tokens.splice(imgStart, imgEnd - imgStart + 1, divNumber, ...img, getCloseToken('div', level - 1));\n\n        const divToken = getOpenToken('div', level - 1);\n        divToken.attrPush(['class', 'benefit']);\n        tokens.splice(ctxStart + 2, ctxEnd - ctxStart + 3, divToken, ...context, getCloseToken('div', level - 1));\n        return state;\n    },\n    render(tokens, idx) {\n        const token = tokens[idx];\n        if (token.nesting === 1) {\n            const cmIndex = token.attrIndex('css-module');\n            let clsIndex = token.attrIndex('class');\n            let attrs = token.attrs || [];\n\n            if (clsIndex >= 0) {\n                attrs[clsIndex][1] += cmIndex >= 0 ? ` cta ${attrs[cmIndex][1]}` : ` cta`;\n            } else {\n                attrs.push(['class', cmIndex >= 0 ? ` cta ${attrs[cmIndex][1]}` : ` cta`]);\n            }\n\n            attrs = attrs.map(([key, value]) => {\n                return `${key}=\"${value}\"`;\n            });\n            // opening tag\n            return `<div ${attrs.join(' ')}>\\n`;\n        } else {\n            // closing tag\n            return '</div>\\n';\n        }\n    }\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/containers/div.js",
    "content": "const {getAttrs, getAttrsString} = require('../attrs/utils');\n\nmodule.exports = {\n    validate(params) {\n        return true;\n    },\n    handler(state, opts) {\n        return state;\n    },\n    render(tokens, idx) {\n        const token = tokens[idx];\n        let tag = 'div';\n        if (['footer', 'note', 'header'].includes(token.info)) {\n            tag = token.info;\n        }\n        if (token.nesting === 1) {\n            let attrs = token.attrs || [];\n            attrs = attrs.map(([key, value]) => {\n                return `${key}=\"${value}\"`;\n            });\n            // opening tag\n            return `<${tag} ${attrs.join(' ')}>\\n`;\n        } else {\n            // closing tag\n            return `</${tag}>\\n`;\n        }\n    }\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/containers/features.js",
    "content": "const name = 'features';\n\nmodule.exports = {\n    validate(params) {\n        return params.trim().match(new RegExp('^' + name + '\\\\s*(.*)$'));\n    },\n    handler(state, opts, start) {\n        function getOpenToken(tag, level) {\n            const token = new state.Token('container_' + name + '_' + tag + '_open', tag, 1);\n            token.block = true;\n            token.level = 1 + level;\n            return token;\n        }\n        function getCloseToken(tag, level) {\n            const token = new state.Token('container_' + name + '_' + tag + '_close', tag, -1);\n            token.block = true;\n            token.level = 1 + level;\n            return token;\n        }\n        // tokens\n        const tokens = state.tokens;\n\n        let open = false;\n        let done = 0;\n        for (let i = start; i < tokens.length; i++) {\n            const token = tokens[i];\n            if (token.type === 'container_' + name + '_open') {\n                // 在 open 后面插入\n                tokens.splice(i + 1, 0, getOpenToken('li', token.level), getOpenToken('div', token.level + 1));\n                open = true;\n                i = i + 2;\n            } else if (token.type === 'container_' + name + '_close') {\n                // 在 close 之前插入\n                tokens.splice(i, 0, getCloseToken('div', token.level + 1), getCloseToken('li', token.level));\n                open = false;\n                i = i + 2;\n            } else if (open && 'hr' === token.type && done === 0) {\n                // 第一层的 Hr 需要替换\n                tokens.splice(\n                    i,\n                    1,\n                    getCloseToken('div', token.level),\n                    getCloseToken('li', token.level - 1),\n                    getOpenToken('li', token.level - 1),\n                    getOpenToken('div', token.level)\n                );\n                i = i + 3;\n            } else if (open) {\n                // 加深一层，因为外面多套了一层div\n                token.level = token.level + 2;\n                // 保证hr 是最贴近 container 的一层\n                if (/_open$/.test(token.type)) {\n                    done++;\n                } else if (/_close$/.test(token.type)) {\n                    done--;\n                }\n            }\n        }\n        return state;\n    },\n    render(tokens, idx) {\n        const token = tokens[idx];\n\n        if (token.nesting === 1) {\n            const cmIndex = token.attrIndex('css-module');\n            let clsIndex = token.attrIndex('class');\n            let attrs = token.attrs || [];\n\n            if (clsIndex >= 0) {\n                attrs[clsIndex][1] +=\n                    cmIndex >= 0 ? ` flexblock features ${attrs[cmIndex][1]}` : ` flexblock features`;\n            } else {\n                attrs.push([\n                    'class',\n                    cmIndex >= 0 ? `flexblock features ${attrs[cmIndex][1]}` : `flexblock features`\n                ]);\n            }\n\n            attrs = attrs.map(([key, value]) => {\n                return `${key}=\"${value}\"`;\n            });\n            // opening tag\n            return `<ul ${attrs.join(' ')}>\\n`;\n        } else {\n            // closing tag\n            return '</ul>\\n';\n        }\n    }\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/containers/flexblock.js",
    "content": "const utils = require('../attrs/utils');\nconst attrOptions = utils.getOptions();\nmodule.exports = {\n    validate(params) {\n        return params.trim().match(/^flexblock\\s*(.*)$/);\n    },\n    handler(state, opts, start, len) {\n        function getOpenToken(tag, level, block = true, attrs = []) {\n            const token = new state.Token('container_flexblock_item_' + tag + '_open', tag, 1);\n            token.block = block;\n            token.attrs = attrs;\n            token.level = 1 + level;\n            return token;\n        }\n        function getCloseToken(tag, level, block = true) {\n            const token = new state.Token('container_flexblock_item_' + tag + '_close', tag, -1);\n            token.block = block;\n            token.level = 1 + level;\n            return token;\n        }\n        // tokens\n        const tokens = state.tokens;\n\n        opts = opts.split(/flexblock\\s+/).splice(1)[0];\n        let attrs = [];\n        let flexblockClass = '';\n        if (utils.hasDelimiters('only', attrOptions)(opts, attrOptions)) {\n            attrs = utils.getAttrs(opts, 0, attrOptions);\n            flexblockClass = utils.findAttrs(attrs, 'class');\n        }\n\n        let open = false;\n        // 支持多个，[[start,level,  [item_tokens],end],[],[]...]\n        const items = [];\n        let itemsIdx = 0;\n\n        for (let i = start; i < tokens.length; i++) {\n            const token = tokens[i];\n            // 分组\n            if (token.type === 'container_flexblock_open') {\n                open = true;\n                items[itemsIdx] = [i, token.level];\n            } else if (token.type === 'container_flexblock_close') {\n                open = false;\n                // end\n                items[itemsIdx][3] = i - 1;\n            } else if (open && 'hr' === token.type) {\n                items[itemsIdx++][3] = i;\n                items[itemsIdx] = [i, token.level];\n            } else if (open) {\n                items[itemsIdx][2] = items[itemsIdx][2] || [];\n                // 全部先加深一层\n                token.level += 1;\n                items[itemsIdx][2].push(token);\n            }\n        }\n        // console.log(tokens[start], tokens[tokens.length - 1]);\n        for (let i = items.length - 1; i >= 0; i--) {\n            // 从后往前处理，可以避免i变化\n            const [start, level, itemArray, end] = items[i];\n            // console.log(items[i])\n            let openTag = getOpenToken('li', level);\n            let closeTag = getCloseToken('li', level);\n            let addCount = 2; // 增加额外几个标签\n            // 去除只有p包裹的外层p元素\n            if (itemArray[0].type === 'paragraph_open' && itemArray[itemArray.length - 1].type === 'paragraph_close') {\n                let find = false;\n                for (let i = 1; i < itemArray.length - 1; i++) {\n                    const token = itemArray[i];\n                    if (token.type === 'paragraph_open') {\n                        find = true;\n                        break;\n                    }\n                }\n                if (!find) {\n                    // 说明是唯一哦~，那么就要扒皮了\n                    addCount -= 2;\n                    itemArray.shift();\n                    itemArray.pop();\n                }\n            }\n            if (flexblockClass.indexOf('blink') >= 0 && flexblockClass.indexOf('features') === -1) {\n                // 增加a标签\n                let linkTag = getOpenToken('a', level);\n                itemArray.unshift(linkTag);\n                itemArray.unshift(openTag);\n\n                let linkCloseTag = getCloseToken('a', level);\n                itemArray.push(linkCloseTag);\n                itemArray.push(closeTag);\n                addCount += 2;\n            } else if (flexblockClass.indexOf('specs') >= 0) {\n                // 增加div标签\n                let linkTag = getOpenToken('div', level);\n                itemArray.unshift(linkTag);\n                itemArray.unshift(openTag);\n\n                let linkCloseTag = getCloseToken('div', level);\n                itemArray.push(linkCloseTag);\n                itemArray.push(closeTag);\n                addCount += 2;\n            } else if (flexblockClass.indexOf('clients') >= 0) {\n                // 偷懒的方法，直接取前三个\n                itemArray.splice(0, 1, getOpenToken('figure', level));\n                itemArray[2].type = 'text';\n                itemArray[2].content = '';\n                itemArray.splice(2, 0, getOpenToken('figcaption', level));\n                // 增加a标签\n                let linkTag = getOpenToken('a', level);\n                itemArray.unshift(linkTag);\n                itemArray.unshift(openTag);\n\n                itemArray.push(getCloseToken('figcaption', level));\n\n                itemArray.push(getCloseToken('figure', level));\n                let linkCloseTag = getCloseToken('a', level);\n                itemArray.push(linkCloseTag);\n                itemArray.push(closeTag);\n                addCount += 5;\n            } else if (flexblockClass.indexOf('features') >= 0) {\n                // 增加div标签\n                let linkTag = getOpenToken('div', level);\n                itemArray.unshift(linkTag);\n\n                if (flexblockClass.indexOf('blink') >= 0) {\n                    itemArray.unshift(getOpenToken('a', level));\n                    addCount++;\n                }\n                itemArray.unshift(openTag);\n\n                let linkCloseTag = getCloseToken('div', level);\n                itemArray.push(linkCloseTag);\n                itemArray.push(closeTag);\n                if (flexblockClass.indexOf('blink') >= 0) {\n                    itemArray.push(getCloseToken('a', level));\n                    addCount++;\n                }\n                addCount += 2;\n            } else {\n                // 默认的\n                itemArray.unshift(openTag);\n                itemArray.push(closeTag);\n            }\n            tokens.splice(start + 1, end - start, ...itemArray);\n        }\n        // console.log(tokens[start], tokens[tokens.length - 1]);\n        // console.log(tokens.slice(start));\n    },\n    render(tokens, idx) {\n        const token = tokens[idx];\n\n        if (token.nesting === 1) {\n            const cmIndex = token.attrIndex('css-module');\n            let clsIndex = token.attrIndex('class');\n            let attrs = token.attrs || [];\n            if (clsIndex >= 0) {\n                attrs[clsIndex][1] += cmIndex >= 0 ? ` flexblock ${attrs[cmIndex][1]}` : ` flexblock`;\n            } else {\n                attrs.push(['class', cmIndex >= 0 ? `flexblock ${attrs[cmIndex][1]}` : `flexblock`]);\n            }\n            attrs = attrs.map(([key, value]) => {\n                return `${key}=\"${value}\"`;\n            });\n            // opening tag\n            return `<ul ${attrs.join(' ')}>\\n`;\n        } else {\n            // closing tag\n            return '</ul>\\n';\n        }\n    }\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/containers/flexbox.js",
    "content": "module.exports = (name, clss) => {\n    if (!clss) {\n        // 默认是 name\n        clss = name;\n    }\n    return {\n        validate(params) {\n            return params.trim().match(new RegExp('^' + name + '\\\\s*(.*)$'));\n        },\n        handler(state, opts, start, end) {\n            function getOpenToken(level) {\n                const token = new state.Token('container_' + name + '_item_open', 'li', 1);\n                token.block = true;\n                token.level = 1 + level;\n                return token;\n            }\n            function getCloseToken(level) {\n                const token = new state.Token('container_' + name + '_item_close', 'li', -1);\n                token.block = true;\n                token.level = 1 + level;\n                return token;\n            }\n            // tokens\n            const tokens = state.tokens;\n            let open = false;\n            let done = 0;\n            // console.log(5555,opts, tokens)\n            for (let i = start; i < tokens.length; i++) {\n                const token = tokens[i];\n                if (token.type === 'container_' + name + '_open') {\n                    // 在 open 后面插入\n                    tokens.splice(i + 1, 0, getOpenToken(token.level));\n                    i++;\n                    // console.log(666666);\n                    open = true;\n                    continue;\n                } else if (token.type === 'container_' + name + '_close') {\n                    // 在 close 之前插入\n                    tokens.splice(i, 0, getCloseToken(token.level));\n                    i++;\n                    open = false;\n                    continue;\n                } else if (open && 'hr' === token.type && done === 0) {\n                    // 第一层的 Hr 需要替换\n                    // console.log(77777);\n                    tokens.splice(i, 1, getCloseToken(token.level - 1), getOpenToken(token.level - 1));\n                    i++;\n                } else if (open) {\n                    if (token.type === 'paragraph_close' || token.type === 'paragraph_open') {\n                        tokens.splice(i, 1);\n                        i--;\n                        continue;\n                    }\n                    // 加深一层，因为外面多套了一层div\n                    token.level = token.level + 1;\n                    // 保证hr 是最贴近 container 的一层\n                    if (/_open$/.test(token.type)) {\n                        done++;\n                    } else if (/_close$/.test(token.type)) {\n                        done--;\n                    }\n                }\n            }\n            // console.log(tokens);\n            return state;\n        },\n        render(tokens, idx) {\n            const token = tokens[idx];\n\n            if (token.nesting === 1) {\n                const cmIndex = token.attrIndex('css-module');\n                let clsIndex = token.attrIndex('class');\n                let attrs = token.attrs || [];\n\n                if (clsIndex >= 0) {\n                    attrs[clsIndex][1] +=\n                        cmIndex >= 0 ? ` flexblock ${clss} ${attrs[cmIndex][1]}` : ` flexblock ${clss}`;\n                } else {\n                    attrs.push([\n                        'class',\n                        cmIndex >= 0 ? `flexblock ${clss} ${attrs[cmIndex][1]}` : `flexblock ${clss}`\n                    ]);\n                }\n\n                attrs = attrs.map(([key, value]) => {\n                    return `${key}=\"${value}\"`;\n                });\n                // opening tag\n                return `<ul ${attrs.join(' ')}>\\n`;\n            } else {\n                // closing tag\n                return '</ul>\\n';\n            }\n        }\n    };\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/containers/gallery.js",
    "content": "const name = 'gallery';\n\nmodule.exports = {\n    validate(params) {\n        return params.trim().match(/^gallery(|-overlay)\\s*(.*)$/);\n    },\n    handler(state, opts) {\n        const m = opts.match(/^gallery(|-overlay)\\s*(.*)$/);\n        if (m && m[1]) {\n            opts = m[1].trim();\n        }\n        function getOpenToken(tag, level) {\n            const token = new state.Token('container_' + name + '_' + tag + '_open', tag, 1);\n            token.block = true;\n            token.level = 1 + level;\n            return token;\n        }\n        function getCloseToken(tag, level) {\n            const token = new state.Token('container_' + name + '_' + tag + '_close', tag, -1);\n            token.block = true;\n            token.level = 1 + level;\n            return token;\n        }\n        // tokens\n        const tokens = state.tokens;\n        // console.log(opts);\n        let open = false;\n        let done = 0;\n        for (let i = 0; i < tokens.length; i++) {\n            const token = tokens[i];\n            if (token.type === 'container_' + name + '_open') {\n                // 在 open 后面插入\n                tokens.splice(i + 1, 0, getOpenToken('li', token.level), getOpenToken('a', token.level + 1));\n                open = true;\n                i = i + 2;\n            } else if (token.type === 'container_' + name + '_close') {\n                // 在 close 之前插入\n                tokens.splice(i, 0, getCloseToken('a', token.level + 1), getCloseToken('li', token.level));\n                open = false;\n                i = i + 2;\n            } else if (open && 'hr' === token.type && done === 0) {\n                // 第一层的 Hr 需要替换\n                tokens.splice(\n                    i,\n                    1,\n                    getCloseToken('a', token.level),\n                    getCloseToken('li', token.level - 1),\n                    getOpenToken('li', token.level - 1),\n                    getOpenToken('a', token.level)\n                );\n                i = i + 3;\n            } else if (open) {\n                // 加深一层，因为外面多套了一层div\n                token.level = token.level + 2;\n                // 保证hr 是最贴近 container 的一层\n                if (/_open$/.test(token.type)) {\n                    done++;\n                } else if (/_close$/.test(token.type)) {\n                    done--;\n                }\n\n                // 确定下一个是 img\n                const nextToken = tokens[i + 1];\n                if (\n                    token.type === 'paragraph_open' &&\n                    nextToken.type === 'inline' &&\n                    /^\\!\\[]\\(.+\\)$/.test(nextToken.content)\n                ) {\n                    let level = token.level - 1;\n                    // 替换掉 paragraph_open\n                    tokens.splice(i, 1, getOpenToken('figure', level));\n                    // 替换掉 paragraph_close\n                    tokens.splice(i + 2, 1, getCloseToken('figure', level - 2));\n\n                    const tt = [];\n                    for (let j = i + 3; j < tokens.length; j++) {\n                        const temp = tokens[j];\n                        if (temp.type === 'hr' || temp.type === 'container_gallery_close') {\n                            tokens.splice(i + 3, tt.length);\n                            let openTag, closeTag;\n                            if (opts === 'gallery-overlay') {\n                                openTag = getOpenToken('div', token.level - 2);\n                                closeTag = getCloseToken('div', token.level - 2);\n                                openTag.attrPush(['class', 'overlay']);\n                            } else {\n                                openTag = getOpenToken('figcaption', token.level - 2);\n                                closeTag = getCloseToken('figcaption', token.level - 2);\n                            }\n                            // 到头了\n                            tokens.splice(i + 2, 0, openTag, ...tt, closeTag);\n                            // i = i + 4 + tt.length;\n                            break;\n                        }\n\n                        // 加深2层\n                        temp.level = temp.level + 2;\n                        tt.push(temp);\n                    }\n                }\n            }\n        }\n        // console.log(tokens);\n        return state;\n    },\n    render(tokens, idx) {\n        const token = tokens[idx];\n\n        if (token.nesting === 1) {\n            const cmIndex = token.attrIndex('css-module');\n            let clsIndex = token.attrIndex('class');\n            let attrs = token.attrs || [];\n\n            if (clsIndex >= 0) {\n                attrs[clsIndex][1] += cmIndex >= 0 ? ` flexblock gallery ${attrs[cmIndex][1]}` : ` flexblock gallery`;\n            } else {\n                attrs.push(['class', cmIndex >= 0 ? `flexblock gallery ${attrs[cmIndex][1]}` : `flexblock gallery`]);\n            }\n\n            attrs = attrs.map(([key, value]) => {\n                return `${key}=\"${value}\"`;\n            });\n            // opening tag\n            return `<ul ${attrs.join(' ')}>\\n`;\n        } else {\n            // closing tag\n            return '</ul>\\n';\n        }\n    }\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/containers/shadow.js",
    "content": "const name = 'shadowbox';\n\nmodule.exports = {\n    validate(params) {\n        return params.trim().match(/^shadowbox\\s*(.*)$/);\n    },\n    handler(state, opts) {\n        function getOpenToken(level) {\n            const token = new state.Token('container_' + name + '_item_open', 'li', 1);\n            token.block = true;\n            token.level = 1 + level;\n            return token;\n        }\n        function getCloseToken(level) {\n            const token = new state.Token('container_' + name + '_item_close', 'li', -1);\n            token.block = true;\n            token.level = 1 + level;\n            return token;\n        }\n        // tokens\n        const tokens = state.tokens;\n\n        let open = false;\n        let done = 0;\n        for (let i = 0; i < tokens.length; i++) {\n            const token = tokens[i];\n            if (token.type === 'container_' + name + '_open') {\n                // 在 open 后面插入\n                tokens.splice(i + 1, 0, getOpenToken(token.level));\n                open = true;\n                i++;\n            } else if (token.type === 'container_' + name + '_close') {\n                // 在 close 之前插入\n                tokens.splice(i, 0, getCloseToken(token.level));\n                open = false;\n                i++;\n            } else if (open && 'hr' === token.type && done === 0) {\n                // 第一层的 Hr 需要替换\n                tokens.splice(i, 1, getCloseToken(token.level - 1), getOpenToken(token.level - 1));\n                i++;\n            } else if (open) {\n                // 加深一层，因为外面多套了一层div\n                token.level = token.level + 1;\n                // 保证hr 是最贴近 container 的一层\n                if (/_open$/.test(token.type)) {\n                    done++;\n                } else if (/_close$/.test(token.type)) {\n                    done--;\n                }\n            }\n        }\n        return state;\n    },\n    render(tokens, idx) {\n        const token = tokens[idx];\n\n        if (token.nesting === 1) {\n            const cmIndex = token.attrIndex('css-module');\n            let clsIndex = token.attrIndex('class');\n            let attrs = token.attrs || [];\n\n            if (clsIndex >= 0) {\n                attrs[clsIndex][1] += cmIndex >= 0 ? ` bg-white shadow ${attrs[cmIndex][1]}` : ' bg-white shadow';\n            } else {\n                attrs.push(['class', cmIndex >= 0 ? `bg-white shadow ${attrs[cmIndex][1]}` : 'bg-white shadow']);\n            }\n\n            attrs = attrs.map(([key, value]) => {\n                return `${key}=\"${value}\"`;\n            });\n            // opening tag\n            return `<div ${attrs.join(' ')}><ul class=\"flexblock reasons\">\\n`;\n        } else {\n            // closing tag\n            return '</ul></div>\\n';\n        }\n    }\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/containers/specs.js",
    "content": "const name = 'specs';\n\nmodule.exports = {\n    validate(params) {\n        return params.trim().match(new RegExp('^' + name + '\\\\s*(.*)$'));\n    },\n    handler(state, opts, start) {\n        function getOpenToken(tag, level) {\n            const token = new state.Token('container_' + name + '_' + tag + '_open', tag, 1);\n            token.block = true;\n            token.level = 1 + level;\n            return token;\n        }\n        function getCloseToken(tag, level) {\n            const token = new state.Token('container_' + name + '_' + tag + '_close', tag, -1);\n            token.block = true;\n            token.level = 1 + level;\n            return token;\n        }\n        // tokens\n        const tokens = state.tokens;\n\n        let open = false;\n        let done = 0;\n        for (let i = 0; i < tokens.length; i++) {\n            const token = tokens[i];\n            if (token.type === 'container_' + name + '_open') {\n                // 在 open 后面插入\n                tokens.splice(i + 1, 0, getOpenToken('li', token.level), getOpenToken('div', token.level + 1));\n                open = true;\n                i = i + 2;\n            } else if (token.type === 'container_' + name + '_close') {\n                // 在 close 之前插入\n                tokens.splice(i, 0, getCloseToken('div', token.level + 1), getCloseToken('li', token.level));\n                open = false;\n                i = i + 2;\n            } else if (open && 'hr' === token.type && done === 0) {\n                // 第一层的 Hr 需要替换\n                tokens.splice(\n                    i,\n                    1,\n                    getCloseToken('div', token.level),\n                    getCloseToken('li', token.level - 1),\n                    getOpenToken('li', token.level - 1),\n                    getOpenToken('div', token.level)\n                );\n                i = i + 3;\n            } else if (open) {\n                // 加深一层，因为外面多套了一层div\n                token.level = token.level + 2;\n                // 保证hr 是最贴近 container 的一层\n                if (/_open$/.test(token.type)) {\n                    done++;\n                } else if (/_close$/.test(token.type)) {\n                    done--;\n                }\n            }\n        }\n        return state;\n    },\n    render(tokens, idx) {\n        const token = tokens[idx];\n\n        if (token.nesting === 1) {\n            const cmIndex = token.attrIndex('css-module');\n            let clsIndex = token.attrIndex('class');\n            let attrs = token.attrs || [];\n\n            if (clsIndex >= 0) {\n                attrs[clsIndex][1] +=\n                    cmIndex >= 0 ? ` flexblock specs ${attrs[cmIndex][1]}` : ` flexblock specs`;\n            } else {\n                attrs.push([\n                    'class',\n                    cmIndex >= 0 ? `flexblock specs ${attrs[cmIndex][1]}` : `flexblock specs`\n                ]);\n            }\n\n            attrs = attrs.map(([key, value]) => {\n                return `${key}=\"${value}\"`;\n            });\n            // opening tag\n            return `<ul ${attrs.join(' ')}>\\n`;\n        } else {\n            // closing tag\n            return '</ul>\\n';\n        }\n    }\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/containers/steps.js",
    "content": "const name = 'steps';\n\nmodule.exports = {\n    validate(params) {\n        return params.trim().match(new RegExp('^' + name + '\\\\s*(.*)$'));\n    },\n    handler(state, opts, start) {\n        function getOpenToken(tag, level) {\n            const token = new state.Token('container_' + name + '_' + tag + '_open', tag, 1);\n            token.block = true;\n            token.level = 1 + level;\n            return token;\n        }\n        function getCloseToken(tag, level) {\n            const token = new state.Token('container_' + name + '_' + tag + '_close', tag, -1);\n            token.block = true;\n            token.level = 1 + level;\n            return token;\n        }\n        // tokens\n        const tokens = state.tokens;\n\n        let open = false;\n        let done = 0;\n        let step = 1;\n        for (let i = start; i < tokens.length; i++) {\n            const token = tokens[i];\n            if (token.type === 'container_' + name + '_open') {\n                // 在 open 后面插入\n                tokens.splice(i + 1, 0, getOpenToken('li', token.level));\n                open = true;\n                i++;\n            } else if (token.type === 'container_' + name + '_close') {\n                // 在 close 之前插入\n                tokens.splice(i, 0, getCloseToken('li', token.level));\n                open = false;\n                i++;\n            } else if (open && 'hr' === token.type && done === 0) {\n                // 第一层的 Hr 需要替换\n                tokens.splice(i, 1, getCloseToken('li', token.level - 1), getOpenToken('li', token.level - 1));\n                i++;\n\n                step++;\n                if (step >= 2) {\n                    let t1 = getOpenToken('div', token.level);\n                    t1.attrPush(['class', `process step-${step}`]);\n                    tokens.splice(i + 1, 0, t1, getCloseToken('div', token.level));\n                    i = i + 2;\n                }\n            } else if (open) {\n                // 加深一层，因为外面多套了一层div\n                token.level = token.level + 2;\n                // 保证hr 是最贴近 container 的一层\n                if (/_open$/.test(token.type)) {\n                    done++;\n                } else if (/_close$/.test(token.type)) {\n                    done--;\n                }\n            }\n        }\n        return state;\n    },\n    render(tokens, idx) {\n        const token = tokens[idx];\n\n        if (token.nesting === 1) {\n            const cmIndex = token.attrIndex('css-module');\n            let clsIndex = token.attrIndex('class');\n            let attrs = token.attrs || [];\n\n            if (clsIndex >= 0) {\n                attrs[clsIndex][1] += cmIndex >= 0 ? ` flexblock steps ${attrs[cmIndex][1]}` : ` flexblock steps`;\n            } else {\n                attrs.push(['class', cmIndex >= 0 ? `flexblock steps ${attrs[cmIndex][1]}` : `flexblock steps`]);\n            }\n\n            attrs = attrs.map(([key, value]) => {\n                return `${key}=\"${value}\"`;\n            });\n            // opening tag\n            return `<ul ${attrs.join(' ')}>\\n`;\n        } else {\n            // closing tag\n            return '</ul>\\n';\n        }\n    }\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/echarts.js",
    "content": "module.exports = md => {\n    const temp = md.renderer.rules.fence.bind(md.renderer.rules);\n    md.renderer.rules.fence = (tokens, idx, options, env, slf) => {\n        const token = tokens[idx];\n        const code = token.content.trim();\n        if (token.info === 'echarts') {\n            try {\n                // 这里特殊处理下！\n                const json = (1, eval)(`(function(){return ${code};})()`);\n                let attrs = token.attrs || [];\n                attrs = attrs.map(([key, value]) => {\n                    return `${key}=\"${value}\"`;\n                }).join(' ')\n                return `<div class=\"embed\"><div class=\"echarts\" ${attrs}></div></div><div class=\"echarts-data\" style='display:none'>${JSON.stringify(\n                    json\n                )}</div>`;\n            } catch (err) {\n                return `<pre>${err}</pre>`;\n            }\n        }\n        return temp(tokens, idx, options, env, slf);\n    };\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/fa.js",
    "content": "const mditr = require('./regexp');\n\nmodule.exports = md => {\n    // FA4 style.\n    md.use(\n        mditr(\n            /\\:fa-(.+?)\\:/,\n            (match, utils) => {\n                return '<i class=\"fa fa-' + utils.escape(match[1]) + '\"></i>';\n            },\n            'fa_inline'\n        )\n    );\n\n    md.use(\n        mditr(\n            /\\:\\~fa-(.+?)~\\:/,\n            (match, utils) => {\n                return '<span><i class=\"fa fa-' + utils.escape(match[1]) + '\"></i></span>';\n            },\n            'fa_span'\n        )\n    );\n\n    md.use(\n        mditr(\n            /\\:\\:fa-(.+?)\\:\\:/,\n            (match, utils) => {\n                return '<i class=\"fa fa-' + utils.escape(match[1]) + '\"></i>';\n            },\n            'fa_block'\n        )\n    );\n\n    md.core.ruler.push('fa_blockify', function(state) {\n        var paragraphTokensToRemove = [];\n\n        var lastInlineTokenSeen;\n        for (var blkIdx = 0; blkIdx < state.tokens.length; blkIdx++) {\n            if (state.tokens[blkIdx].type !== 'paragraph_open') {\n                continue;\n            }\n\n            var nextBlkToken = state.tokens[blkIdx + 1];\n            if (nextBlkToken.type !== 'inline') {\n                continue;\n            }\n\n            // FIXME Incorrect and a hack:\n            // <p><Component> ... </OtherComponent></p> will also get stripped.\n            var paragraphOpens = 0;\n            for (var i = blkIdx + 1; i < state.tokens.length; i++) {\n                if (state.tokens[i].type === 'paragraph_open') {\n                    paragraphOpens++;\n                    continue;\n                } else if (state.tokens[i].type !== 'paragraph_close') {\n                    continue;\n                }\n\n                if (paragraphOpens > 0) {\n                    paragraphOpens--;\n                    continue;\n                }\n\n                // OK, this is the paragraph_close matching the open we started on.\n                // What came right before here?\n                var prevBlkToken = state.tokens[i - 1];\n\n                if (prevBlkToken.type !== 'inline') {\n                    break;\n                }\n                var prevInlineToken = prevBlkToken.children[prevBlkToken.children.length - 1];\n\n                if (prevInlineToken.type !== 'fa_block' && prevInlineToken.type !== 'fa_span') {\n                    break;\n                }\n\n                // If we got this far, we're stripping the surrounding paragraph.\n\n                // FIXME Also a hack. The 'inline' JSX that's inside the paragraph should\n                // now get a linebreak after it in the generated HTML. Easier to test\n                // and looks better in the HTML.\n                prevInlineToken.content += '\\n';\n                paragraphTokensToRemove.push(blkIdx, i);\n                break;\n            }\n        }\n\n        state.tokens = state.tokens.filter(function(blkToken, idx) {\n            return paragraphTokensToRemove.indexOf(idx) === -1;\n        });\n    });\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/img.js",
    "content": "const mditr = require('./regexp');\n\nconst {getAttrs, getAttrsString} = require('./attrs/utils');\n\nmodule.exports = md => {\n    md.use(\n        mditr(\n            /!!\\[(\\w+|)(.*?)?]\\((.+?)\\)/,\n            (match, utils) => {\n                let attrs = match[2] || '';\n                if (attrs) {\n                    attrs = getAttrsString(\n                        getAttrs(`{${attrs}}`, 0, {\n                            leftDelimiter: '{',\n                            rightDelimiter: '}'\n                        })\n                    );\n                }\n                let src = match[3];\n                let srcs = src.split(' ');\n                src = srcs.shift();\n                let imgAttrs = '';\n                if (srcs.length) {\n                    imgAttrs = getAttrsString(\n                        getAttrs(`{${srcs.join(' ')}}`, 0, {\n                            leftDelimiter: '{',\n                            rightDelimiter: '}'\n                        })\n                    );\n                }\n                if (match[1]) {\n                    return `<${match[1]} ${attrs}><img src=\"${src}\" ${imgAttrs}/></${match[1]}>`;\n                } else {\n                    return `<img src=\"${src}\" ${imgAttrs}/>`;\n                }\n            },\n            'img_block'\n        )\n    );\n\n    md.core.ruler.push('img_blockify', function(state) {\n        var paragraphTokensToRemove = [];\n\n        var lastInlineTokenSeen;\n        for (var blkIdx = 0; blkIdx < state.tokens.length; blkIdx++) {\n            if (state.tokens[blkIdx].type !== 'paragraph_open') {\n                continue;\n            }\n\n            var nextBlkToken = state.tokens[blkIdx + 1];\n            if (nextBlkToken.type !== 'inline') {\n                continue;\n            }\n\n            // FIXME Incorrect and a hack:\n            // <p><Component> ... </OtherComponent></p> will also get stripped.\n            var paragraphOpens = 0;\n            for (var i = blkIdx + 1; i < state.tokens.length; i++) {\n                if (state.tokens[i].type === 'paragraph_open') {\n                    paragraphOpens++;\n                    continue;\n                } else if (state.tokens[i].type !== 'paragraph_close') {\n                    continue;\n                }\n\n                if (paragraphOpens > 0) {\n                    paragraphOpens--;\n                    continue;\n                }\n\n                var prevBlkToken = state.tokens[i - 1];\n                if (prevBlkToken.type !== 'inline') {\n                    break;\n                }\n                // console.log(prevBlkToken.content)\n                var prevInlineToken = prevBlkToken.children[prevBlkToken.children.length - 1];\n                if (prevInlineToken.type !== 'img_block') {\n                    break;\n                }\n\n                prevInlineToken.content += '\\n';\n                paragraphTokensToRemove.push(blkIdx, i);\n                break;\n            }\n        }\n\n        state.tokens = state.tokens.filter((blkToken, idx) => {\n            return paragraphTokensToRemove.indexOf(idx) === -1;\n        });\n    });\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/jsx.js",
    "content": "'use strict';\n// from https://github.com/osnr/markdown-it-jsx/\nvar jsx_inline = require('markdown-it-jsx/lib/jsx_inline');\nvar escape_code = require('markdown-it-jsx/lib/escape_code');\n\nmodule.exports = function jsx_plugin(md) {\n    md.set({xhtmlOut: true});\n\n    // JSX should entirely replace embedded HTML.\n    md.inline.ruler.before('html_inline', 'jsx_inline', jsx_inline);\n    md.disable('html_inline');\n    // We'll parse blocks as inline and then strip the surrounding paragraph at the end; it's easier.\n    md.disable('html_block');\n\n    md.core.ruler.push('jsx_blockify', function(state) {\n        // Look for things like <p><Component> ... </Component></p> and strip the <p>, </p> there.\n        // FIXME Quadratic time in worst case, I think?\n        var paragraphTokensToRemove = [];\n\n        var lastInlineTokenSeen;\n        for (var blkIdx = 0; blkIdx < state.tokens.length; blkIdx++) {\n            if (state.tokens[blkIdx].type !== 'paragraph_open') {\n                continue;\n            }\n\n            var nextBlkToken = state.tokens[blkIdx + 1];\n            if (nextBlkToken.type !== 'inline' || nextBlkToken.children[0].type !== 'jsx_inline') {\n                continue;\n            }\n\n            // FIXME Incorrect and a hack:\n            // <p><Component> ... </OtherComponent></p> will also get stripped.\n            var paragraphOpens = 0;\n            for (var i = blkIdx + 1; i < state.tokens.length; i++) {\n                if (state.tokens[i].type === 'paragraph_open') {\n                    paragraphOpens++;\n                    continue;\n                } else if (state.tokens[i].type !== 'paragraph_close') {\n                    continue;\n                }\n\n                if (paragraphOpens > 0) {\n                    paragraphOpens--;\n                    continue;\n                }\n\n                // OK, this is the paragraph_close matching the open we started on.\n                // What came right before here?\n                var prevBlkToken = state.tokens[i - 1];\n                if (prevBlkToken.type !== 'inline') {\n                    break;\n                }\n                var prevInlineToken = prevBlkToken.children[prevBlkToken.children.length - 1];\n                if (prevInlineToken.type !== 'jsx_inline') {\n                    break;\n                }\n\n                // If we got this far, we're stripping the surrounding paragraph.\n\n                // FIXME Also a hack. The 'inline' JSX that's inside the paragraph should\n                // now get a linebreak after it in the generated HTML. Easier to test\n                // and looks better in the HTML.\n                prevInlineToken.content += '\\n';\n                paragraphTokensToRemove.push(blkIdx, i);\n                break;\n            }\n        }\n\n        state.tokens = state.tokens.filter(function(blkToken, idx) {\n            return paragraphTokensToRemove.indexOf(idx) === -1;\n        });\n    });\n\n    // 去掉代码注释\n    // md.renderer.rules.fence = escape_code(md.renderer.rules.fence)\n    // md.renderer.rules.code_inline = escape_code(md.renderer.rules.code_inline)\n    // md.renderer.rules.code_block = escape_code(md.renderer.rules.code_block)\n\n    md.renderer.rules['jsx_inline'] = function(tokens, idx) {\n        // If the span is JSX, just pass the original source for the span\n        // through to output.\n        return tokens[idx].content;\n    };\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/link.js",
    "content": "module.exports = md => {\n    const defLinkOpen =\n        md.renderer.rules.link_open ||\n        function(tokens, idx, options, env, self) {\n            return self.renderToken(tokens, idx, options);\n        };\n\n    md.renderer.rules.link_open = function(tokens, idx, options, env, self) {\n        // 增加 target\n        const aIndex = tokens[idx].attrIndex('target');\n        const hrefIndex = tokens[idx].attrIndex('href');\n        if (hrefIndex >= 0) {\n            // 从在 hrefIndex\n            const href = tokens[idx].attrs[hrefIndex][1];\n            if (href && href !== '#') {\n                if (aIndex < 0) {\n                    if (/^http[s]?:\\/\\//.test(href)) {\n                        // 只是添加外链\n                        tokens[idx].attrPush(['target', '_blank']); // add new attribute\n                    }\n                } else {\n                    tokens[idx].attrs[aIndex][1] = '_blank'; // replace value of existing attr\n                }\n            } else {\n                // 空连接不增加 _blank 替换成 JavaScript：void\n                tokens[idx].attrs[hrefIndex][1] = 'javascript:void(0)';\n            }\n        }\n\n        // pass token to default renderer.\n        return defLinkOpen(tokens, idx, options, env, self);\n    };\n};\n\n// module.exports = md => {\n//   md.use(\n//     mditr(\n//       /\\[\\!(\\.\\w+\\s+)?(.+?)]\\((.*?)\\)/,\n//       (match, utils) => {\n//         let cls = match[1] ? match[1].slice(1).trim() : ''\n//         if (cls) {\n//           cls = cls.split('.').join(' ')\n//         }\n//         let content = match[2]\n//         content = md.render(content)\n\n//         return `<a class=\"button ${cls}\" href=\"${\n//           match[3] && match[3] !== '' ? match[3] : 'javascript:void(0)'\n//         }\">${content}</a>`\n//       },\n//       'button_inline'\n//     )\n//   )\n// }\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/mermaid.js",
    "content": "module.exports = md => {\n    const temp = md.renderer.rules.fence.bind(md.renderer.rules);\n    md.renderer.rules.fence = (tokens, idx, options, env, slf) => {\n        const token = tokens[idx];\n        const code = token.content;\n        if (token.info === 'mermaid') {\n            token.attrJoin('class', 'lang-mermaid no-style');\n            let attrs = token.attrs || [];\n            attrs = attrs\n                .map(([key, value]) => {\n                    return `${key}=\"${value}\"`;\n                })\n                .join(' ');\n            // 增加对 mermaidjs 支持，这样就可以画流程图了哦~\n            return `\n<div class=\"embed\">\n    <pre ${attrs}>${code}</pre>\n</div>\n`;\n        }\n        return temp(tokens, idx, options, env, slf);\n    };\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/plus-list.js",
    "content": "function findChecklists(state) {\n    // console.log(state.tokens);\n    state.tokens.forEach((token, i) => {\n        if (token.type === 'bullet_list_open' && token.markup === '+') {\n            // console.log(state.tokens[i + 1]);\n            // applyClass(token);\n        }\n    });\n}\n\n/**\n * Adds class to token\n *\n * @param {any} token\n * @param {string} className\n */\nfunction applyClass(token) {\n    // init attributes\n    token.attrs = token.attrs || [];\n\n    // get index of class attribute\n    let keys = token.attrs.map(arr => arr[0]);\n    let idx = keys.indexOf('class');\n\n    if (idx === -1) {\n        // Add class attribute if not defined\n        token.attrs.push(['class', className]);\n    } else {\n        // Get the current class list to append.\n        // Watch out for duplicates\n        let classStr = token.attrs[idx][1] || '';\n        let classList = classStr.split(' ');\n\n        // Add the class if we don't already have it\n        if (classList.indexOf(className) === -1) {\n            token.attrs[idx][1] = classStr += ' ' + className;\n        }\n    }\n}\n\nmodule.exports = function plugin(md) {\n    // Default class name\n    md.block.ruler.after('list', 'list-checkmarks', state => {\n        findChecklists(state);\n    });\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/prism.js",
    "content": "// https://github.com/jGleitz/markdown-it-prism/\n// 增加attrs支持\nconst Prism = require('prismjs');\n\n/**\n * A callback that can be used to perform custom initialisation of the Prism instance.\n *\n * @callback PrismInitialisationCallback\n * @param {Prism} prism\n *        The Prism instance\n */\n\n/**\n * The options for the markdown-it-prism plugin\n *\n * @typedef {Object} MarkdownItPrismOptions\n * @property {String[]} plugins\n *        Names of Prism plugins to load\n * @property {PrismInitialisationCallback} init\n *        Callback for Prism initialisation\n * @property {String} defaultLanguageForUnknown\n *        The language to use for code blocks that specify a language that Prism does not know\n * @property {String} defaultLanguageForUnspecified\n *        The language to use for code block that do not specify a language\n * @property {String} defaultLanguage\n *        Shorthand to set both {@code defaultLanguageForUnknown} and {@code defaultLanguageForUnspecified} to the same value\n */\nconst DEFAULTS = {\n    plugins: [],\n    init: () => {},\n    defaultLanguageForUnknown: undefined,\n    defaultLanguageForUnspecified: undefined,\n    defaultLanguage: undefined\n};\n\n/**\n * Loads the provided {@code lang} into prism.\n *\n * @param {String} lang\n *        Code of the language to load.\n * @return {Object} The Prism language object for the provided {@code lang} code. {@code undefined} if the language is not known to Prism.\n */\nfunction loadPrismLang(lang) {\n    if (!lang) return undefined;\n    let langObject = Prism.languages[lang];\n    if (langObject === undefined) {\n        try {\n            require('prismjs/components/index')([lang]);\n            return Prism.languages[lang];\n        } catch (e) {\n            // nothing to do\n        }\n    }\n    return langObject;\n}\n\n/**\n * Loads the provided Prism plugin.a\n * @param name\n *        Name of the plugin to load\n * @throws {Error} If there is no plugin with the provided {@code name}\n */\nfunction loadPrismPlugin(name) {\n    try {\n        require(`prismjs/plugins/${name}/prism-${name}`);\n    } catch (e) {\n        throw new Error(`Cannot load Prism plugin \"${name}\". Please check the spelling.`);\n    }\n}\n\n/**\n * Select the language to use for highlighting, based on the provided options and the specified language.\n *\n * @param {Object} options\n *        The options that were used to initialise the plugin.\n * @param {String} lang\n *        Code of the language to highlight the text in.\n * @return {Array} An array where the first element is the name of the language to use, and the second element is the PRISM language object for that language.\n */\nfunction selectLanguage(options, lang) {\n    let langToUse = lang;\n    if (langToUse === '' && options.defaultLanguageForUnspecified !== undefined) {\n        langToUse = options.defaultLanguageForUnspecified;\n    }\n    let prismLang = loadPrismLang(langToUse);\n    if (prismLang === undefined && options.defaultLanguageForUnknown !== undefined) {\n        langToUse = options.defaultLanguageForUnknown;\n        prismLang = loadPrismLang(langToUse);\n    }\n    return [langToUse, prismLang];\n}\n\n/**\n * Highlights the provided text using Prism.\n *\n * @param {MarkdownIt} markdownit\n *        Instance of MarkdownIt Class. This argument is bound in markdownItPrism().\n * @param {MarkdownItPrismOptions} options\n *        The options that have been used to initialise the plugin. This argument is bound in markdownItPrism().\n * @param {String} text\n *        The text to highlight.\n * @param {String} lang\n *        Code of the language to highlight the text in.\n * @return {String} {@code text} wrapped in {@code &lt;pre&gt;} and {@code &lt;code&gt;}, both equipped with the appropriate class (markdown-it’s langPrefix + lang). If Prism knows {@code lang}, {@code text} will be highlighted by it.\n */\nfunction highlight(markdownit, options, text, lang, attrs = []) {\n    attrs = attrs || [];\n    let [langToUse, prismLang] = selectLanguage(options, lang);\n    const code = prismLang ? Prism.highlight(text, prismLang) : markdownit.utils.escapeHtml(text);\n\n    let hasClass = false;\n    let preAttrs = [];\n    attrs = attrs\n        .map(([key, value]) => {\n            if (key === 'class') {\n                hasClass = true;\n                value += langToUse ? ` ${markdownit.options.langPrefix}${langToUse}` : '';\n            } else if (key === 'css-module') {\n                // 单独提取\n                preAttrs.push(['class', value]);\n                return '';\n            }\n            preAttrs.push([key, value]);\n            return `${key}=\"${value}\"`;\n        })\n        .join(' ');\n    if (!hasClass && langToUse) {\n        attrs += `class=\"${markdownit.options.langPrefix}${langToUse}\"`;\n    }\n    langToUse && preAttrs.push(['class', `${markdownit.options.langPrefix}${langToUse}`]);\n    let rs = {};\n    preAttrs.forEach(([key, value]) => {\n        rs[key] = rs[key] ? [rs[key], value].join(' ') : value;\n    });\n    preAttrs = Object.keys(rs)\n        .map(key => {\n            return `${key}=\"${rs[key]}\"`;\n        })\n        .join(' ');\n    return `<pre ${preAttrs}><code ${attrs}>${code}</code></pre>`;\n}\n\n/**\n * Checks whether an option represents a valid Prism language\n *\n * @param {MarkdownItPrismOptions} options\n *        The options that have been used to initialise the plugin.\n * @param optionName\n *        The key of the option insides {@code options} that shall be checked.\n * @throws {Error} If the option is not set to a valid Prism language.\n */\nfunction checkLanguageOption(options, optionName) {\n    const language = options[optionName];\n    if (language !== undefined && loadPrismLang(language) === undefined) {\n        throw new Error(`Bad option ${optionName}: There is no Prism language '${language}'.`);\n    }\n}\n\n/**\n * Initialisation function of the plugin. This function is not called directly by clients, but is rather provided\n * to MarkdownIt’s {@link MarkdownIt.use} function.\n *\n * @param {MarkdownIt} markdownit\n *        The markdown it instance the plugin is being registered to.\n * @param {MarkdownItPrismOptions} useroptions\n *        The options this plugin is being initialised with.\n */\nmodule.exports = function markdownItPrism(markdownit, useroptions) {\n    const options = Object.assign({}, DEFAULTS, useroptions);\n\n    checkLanguageOption(options, 'defaultLanguage');\n    checkLanguageOption(options, 'defaultLanguageForUnknown');\n    checkLanguageOption(options, 'defaultLanguageForUnspecified');\n    options.defaultLanguageForUnknown = options.defaultLanguageForUnknown || options.defaultLanguage;\n    options.defaultLanguageForUnspecified = options.defaultLanguageForUnspecified || options.defaultLanguage;\n\n    options.plugins.forEach(loadPrismPlugin);\n    options.init(Prism);\n\n    replaceFence(markdownit, (...args) => highlight(markdownit, options, ...args));\n};\n\nfunction replaceFence(md, prismHighlight) {\n    const defaultFence = md.renderer.rules.fence;\n    md.renderer.rules.fence = function(tokens, idx, options, env, slf) {\n        let token = tokens[idx],\n            info = token.info ? String(token.info).trim() : '',\n            langName = '',\n            highlighted,\n            i,\n            tmpAttrs,\n            tmpToken;\n\n        if (info) {\n            langName = info.split(/\\s+/g)[0];\n        }\n\n        highlighted = prismHighlight(token.content, langName, token.attrs);\n        if (highlighted.indexOf('<pre') === 0) {\n            return highlighted + '\\n';\n        }\n\n        return defaultFence(tokens, idx, options, env, slf);\n    };\n}\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/regexp/index.js",
    "content": "/*!\n * markdown-it-regexp\n * Copyright (c) 2014 Alex Kocharin\n * MIT Licensed\n */\n\n/**\n * Module dependencies.\n */\n\nvar util = require('util');\nvar stuff = require('./utils');\n\n/**\n * Counter for multi usage.\n */\nvar counter = 0;\n\n/**\n * Expose `Plugin`\n */\n\nmodule.exports = Plugin;\n\n/**\n * Constructor function\n */\n\nfunction Plugin(regexp, replacer, id) {\n    // return value should be a callable function\n    // with strictly defined options passed by markdown-it\n    var self = function(md, options) {\n        self.options = options;\n        self.init(md);\n    };\n\n    // initialize plugin object\n    self.__proto__ = Plugin.prototype;\n\n    // clone regexp with all the flags\n    var flags = (regexp.global ? 'g' : '') + (regexp.multiline ? 'm' : '') + (regexp.ignoreCase ? 'i' : '');\n\n    self.regexp = RegExp('^' + regexp.source, flags);\n\n    // copy init options\n    self.replacer = replacer;\n\n    // this plugin can be inserted multiple times,\n    // so we're generating unique name for it\n    self.id = id ? id : 'regexp-' + counter;\n    counter++;\n\n    return self;\n}\n\nutil.inherits(Plugin, Function);\n\n// function that registers plugin with markdown-it\nPlugin.prototype.init = function(md) {\n    md.inline.ruler.push(this.id, this.parse.bind(this));\n\n    md.renderer.rules[this.id] = this.render.bind(this);\n};\n\nPlugin.prototype.parse = function(state, silent) {\n    // slowwww... maybe use an advanced regexp engine for this\n    var match = this.regexp.exec(state.src.slice(state.pos));\n    if (!match) return false;\n\n    // valid match found, now we need to advance cursor\n    state.pos += match[0].length;\n\n    // don't insert any tokens in silent mode\n    if (silent) return true;\n\n    var token = state.push(this.id, '', 0);\n    token.meta = {match: match};\n\n    return true;\n};\n\nPlugin.prototype.render = function(tokens, id, options, env) {\n    return this.replacer(tokens[id].meta.match, stuff);\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/regexp/utils.js",
    "content": "/*!\n * markdown-it-regexp\n * Copyright (c) 2014 Alex Kocharin\n * MIT Licensed\n */\n\n/**\n * Escape special characters in the given string of html.\n *\n * Borrowed from escape-html component, MIT-licensed\n */\nexports.escape = function(html) {\n    return String(html)\n        .replace(/&/g, '&amp;')\n        .replace(/\"/g, '&quot;')\n        .replace(/'/g, '&#39;')\n        .replace(/</g, '&lt;')\n        .replace(/>/g, '&gt;');\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/markdown/span.js",
    "content": "const Utils = require('./attrs/utils');\n\nconst hasDelimiters = Utils.hasDelimiters('only', Utils.getOptions());\n\n// 修改 https://github.com/pnewell/markdown-it-span/blob/master/index.js\nmodule.exports = function ins_plugin(md) {\n    // Insert each marker as a separate text token, and add it to delimiter list\n    //\n    function tokenize(state, silent) {\n        var i,\n            scanned,\n            token,\n            len,\n            ch,\n            start = state.pos,\n            marker = state.src.charCodeAt(start);\n\n        if (silent) {\n            return false;\n        }\n\n        if (marker !== 0x3a /* : */) {\n            return false;\n        }\n        // 排查 :fa-:\n\n        if (state.src.charCodeAt(start + 1) === 0x66 /* f */ && state.src.charCodeAt(start + 2) === 0x61 /* a */) {\n            return false;\n        }\n        // 排查 ::fa-::\n        // 排查 :~fa-\n\n        if (\n            state.src.charCodeAt(start + 2) === 0x66 /* f */ &&\n            state.src.charCodeAt(start + 3) === 0x61 /* a */ &&\n            (state.src.charCodeAt(start + 1) === 0x3a /* : */ || state.src.charCodeAt(start + 1) === 0x7e) /* ~ */\n        ) {\n            return false;\n        }\n\n        scanned = state.scanDelims(state.pos, true);\n        len = scanned.length;\n        ch = String.fromCharCode(marker);\n\n        if (len < 1) {\n            return false;\n        }\n\n        if (len % 1) {\n            token = state.push('text', '', 0);\n            token.content = ch;\n            len--;\n        }\n\n        for (i = 0; i < len; i += 1) {\n            token = state.push('text', '', 0);\n            token.content = ch + ch;\n\n            state.delimiters.push({\n                marker: marker,\n                jump: i,\n                token: state.tokens.length - 1,\n                level: state.level,\n                end: -1,\n                open: scanned.can_open,\n                close: scanned.can_close\n            });\n        }\n\n        state.pos += scanned.length;\n\n        return true;\n    }\n\n    // Walk through delimiter list and replace text tokens with tags\n    //\n    function postProcess(state) {\n        var i,\n            j,\n            startDelim,\n            endDelim,\n            token,\n            loneMarkers = [],\n            delimiters = state.delimiters,\n            max = state.delimiters.length;\n\n        for (i = 0; i < max; i++) {\n            startDelim = delimiters[i];\n\n            if (startDelim.marker !== 0x3a /* : */) {\n                continue;\n            }\n\n            if (startDelim.end === -1) {\n                continue;\n            }\n\n            endDelim = delimiters[startDelim.end];\n            token = state.tokens[startDelim.token];\n            token.type = 'span_open';\n            token.tag = 'span';\n            token.nesting = 1;\n            token.markup = ':';\n            token.content = '';\n            let jsx = state.tokens[endDelim.token + 1];\n            if (jsx && jsx.type === 'jsx_inline' && hasDelimiters(jsx.content)) {\n                // 说明是{.xxx}样式\n                let attrs = Utils.getAttrs(jsx.content, 0, Utils.getOptions());\n                Utils.addAttrs(attrs, token);\n                // 清理干净\n                jsx.type = 'text';\n                jsx.content = '';\n            }\n\n            token = state.tokens[endDelim.token];\n            token.type = 'span_close';\n            token.tag = 'span';\n            token.nesting = -1;\n            token.markup = ':';\n            token.content = '';\n\n            if (state.tokens[endDelim.token - 1].type === 'text' && state.tokens[endDelim.token - 1].content === ':') {\n                loneMarkers.push(endDelim.token - 1);\n            }\n        }\n\n        // If a marker sequence has an odd number of characters, it's splitted\n        // like this: `~~~~~` -> `~` + `~~` + `~~`, leaving one marker at the\n        // start of the sequence.\n        //\n        // So, we have to move all those markers after subsequent s_close tags.\n        //\n        while (loneMarkers.length) {\n            i = loneMarkers.pop();\n            j = i + 1;\n\n            while (j < state.tokens.length && state.tokens[j].type === 'span_close') {\n                j++;\n            }\n\n            j--;\n\n            if (i !== j) {\n                token = state.tokens[j];\n                state.tokens[j] = state.tokens[i];\n                state.tokens[i] = token;\n            }\n        }\n    }\n\n    md.inline.ruler.before('emphasis', 'span', tokenize);\n    md.inline.ruler2.before('emphasis', 'span', postProcess);\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/tags/attrs.js",
    "content": "const {mergeAttrs} = require('../utils');\n\nmodule.exports = tree => {\n    tree.walk(node => {\n        if (\n            node &&\n            node.tag &&\n            node.content &&\n            node.content.find &&\n            node.content.find(child => {\n                if (child && child.tag && child.attrs && child.attrs['css-module']) {\n                    return true;\n                }\n                return false;\n            })\n        ) {\n            // 说明是有子节点的父节点\n            // 查找三层数据，用于处理\n            const parentNode = node;\n\n            node.content.forEach(node => {\n                if (node.tag && node.attrs && node.attrs['css-module']) {\n                    let ca = node.attrs['css-module'].split(/\\s+/);\n                    let rs = {};\n                    let ex = /^([\\w\\-]+)=('|\"){1}(.+)\\2/;\n                    parentNode.attrs = parentNode.attrs || {};\n                    ca = ca.filter(attr => {\n                        let m = ex.exec(attr);\n                        if (m && m[1]) {\n                            parentNode.attrs[m[1]] = m[3];\n                            return false;\n                        }\n                        return true;\n                    });\n                    parentNode.attrs = mergeAttrs({class: ca.join(' ')}, parentNode.attrs);\n                    delete node.attrs['css-module'];\n                }\n            });\n        }\n\n        return node;\n    });\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/tags/header-footer.js",
    "content": "const utils = require('./utils');\n\nmodule.exports = tree => {\n    let {slideNode, wrapNode} = utils(tree);\n\n    if (wrapNode.content && wrapNode.content.length) {\n        // console.log(wrapNode.content)\n\n        wrapNode.content = wrapNode.content.map(n => {\n            if (n.tag === 'footer' || n.tag === 'header') {\n                n.content = [\n                    {\n                        tag: 'div',\n                        attrs: {\n                            class: 'wrap'\n                        },\n                        content: n.content\n                    }\n                ];\n                if (n.tag === 'header') {\n                    slideNode.content.unshift(n);\n                } else {\n                    slideNode.content.push(n);\n                }\n                return false;\n            }\n            return n;\n        });\n    }\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/tags/note.js",
    "content": "const utils = require('./utils');\n\nmodule.exports = tree => {\n    let {slideNode, wrapNode} = utils(tree);\n\n    if (wrapNode.content && wrapNode.content.length) {\n        // console.log(wrapNode.content)\n\n        wrapNode.content = wrapNode.content.map(n => {\n            if (n.tag === 'note') {\n                n.tag = 'div';\n                let cls = '';\n                if (n.attrs) {\n                    cls = n.attrs.class || '';\n                }\n                n.attrs = n.attrs || {};\n                cls = cls.split(/\\s+/);\n                cls.push('speaker-note');\n\n                n.attrs.class = cls.join(' ');\n                n.content = [\n                    {\n                        tag: 'div',\n                        attrs: {\n                            class: 'wrap'\n                        },\n                        content: n.content\n                    }\n                ];\n                slideNode.content.push(n);\n                return false;\n            }\n            return n;\n        });\n    }\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/tags/slide.js",
    "content": "const {mergeAttrs} = require('../utils');\n\nconst {getAttrs, getAttrsString} = require('../markdown/attrs/utils');\n\n/**\n * <slide image=\"url .abc\" video=\"url .abc poster_url\">\n */\nmodule.exports = tree => {\n    tree.match({tag: 'slide'}, node => {\n        node.tag = 'section';\n        node.attrs = mergeAttrs(\n            {\n                slide: true,\n                class: 'slide'\n            },\n            node.attrs\n        );\n        const attrs = node.attrs;\n        const wrapAttrs = {};\n        for (let i in attrs) {\n            if (i.startsWith(':')) {\n                // 这是 wrap 的样式\n                wrapAttrs[i.slice(1)] = attrs[i];\n            }\n        }\n        if (Object.keys(wrapAttrs).length > 0) {\n            node.content.forEach(node => {\n                if (node && node.tag === 'div' && node.attrs && node.attrs.wrap) {\n                    node.attrs = mergeAttrs(node.attrs, wrapAttrs);\n                }\n            });\n        }\n\n        if (attrs.image) {\n            let [image, ...imgAttrs] = attrs.image.split(/\\s+/);\n            imgAttrs = getAttrs(`{${imgAttrs.join(' ')}}`, 0, {\n                leftDelimiter: '{',\n                rightDelimiter: '}'\n            });\n            const rs = {};\n            let cls = [];\n            let noBackgroundClass = false;\n            if (imgAttrs.length) {\n                imgAttrs.forEach(([key, value]) => {\n                    if (key === 'class') {\n                        cls = value.split('.').map(c => {\n                            if (!['dark', 'light', 'anim'].includes(c)) {\n                                if (\n                                    [\n                                        'top',\n                                        'bottom',\n                                        'right',\n                                        'right-top',\n                                        'right-bottom',\n                                        'center',\n                                        'center-top',\n                                        'center-bottom',\n                                        'left',\n                                        'left-top',\n                                        'left-bottom'\n                                    ].includes(c)\n                                ) {\n                                    noBackgroundClass = true;\n                                }\n                                return `background-${c}`;\n                            }\n\n                            return c;\n                        });\n                    } else {\n                        rs[key] = value;\n                    }\n                });\n            }\n\n            node.content.unshift({\n                tag: 'span',\n                attrs: {\n                    ...rs,\n                    class: [noBackgroundClass ? '' : 'background', ...cls].join(' '),\n                    style: `background-image:url('${image}')`\n                }\n            });\n        } else if (attrs.youtube) {\n            const ybAttrs = getAttrs(`{${attrs.youtube}}`, 0, {\n                leftDelimiter: '{',\n                rightDelimiter: '}'\n            });\n            const rs = {\n                'data-youtube': '',\n            };\n            let cls = [];\n            if (ybAttrs.length) {\n                ybAttrs.forEach(([key, value]) => {\n                    if (key === 'class') {\n                        cls = value.split('.').map(c => {\n                            return c;\n                        });\n                    }else if(key==='id'){\n                        key = 'youtube-id'\n                    }\n                    rs[`data-${key}`] = value.replace(/^[\\'\\\"]|[\\'\\\"]$/g, '');\n                });\n            }\n            if (cls.length === 0) {\n                cls.push('dark');\n            }\n            cls.push('embed');\n            node.content.unshift({\n                tag: 'div',\n                attrs: {class: cls.join(' ')},\n                content: [\n                    {\n                        tag: 'div',\n                        attrs: rs\n                    }\n                ]\n            });\n        } else if (attrs.video) {\n            let [src, ...videoAttrs] = attrs.video.split(/\\s+/);\n            videoAttrs = getAttrs(`{${videoAttrs.join(' ')}}`, 0, {\n                leftDelimiter: '{',\n                rightDelimiter: '}'\n            });\n            let rs = {};\n            let cls = [];\n            if (videoAttrs.length) {\n                videoAttrs.forEach(([key, value]) => {\n                    if (key === 'class') {\n                        cls = value.split('.').map(c => {\n                            if (!['dark', 'light'].includes(c)) {\n                                return `background-video-${c}`;\n                            }\n                            return c;\n                        });\n                    } else {\n                        // console.log(value);\n                        rs[key] = value.replace(/^\\'|\\'$/g, '');\n                    }\n                });\n            }\n            rs = Object.assign(rs, {loop: true, muted: true});\n            const videoAttr = {\n                ...rs,\n                autoplay: 'autoplay',\n                preload: 'true',\n                class: ['background-video', ...cls].join(' ')\n            };\n            // 支持多个\n            src = src.split(',').map(s => {\n                return {tag: 'source', attrs: {src: s}};\n            });\n            node.content.unshift({\n                tag: 'video',\n                attrs: videoAttr,\n                content: src\n            });\n            /**\n   * <video class=\"background-video dark\" loop=\"\" muted=\"\" poster=\"https://webslides.tv/static/images/peggy.jpg\">\n            <source src=\"https://webslides.tv/static/videos/peggy.mp4\" type=\"video/mp4\">\n          </video>\n   */\n        }\n        return node;\n    });\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/tags/utils.js",
    "content": "module.exports = tree => {\n    let slide;\n    tree.match({tag: 'section'}, node => {\n        if (node.attrs && node.attrs.slide) {\n            slide = node;\n        }\n        return node;\n    });\n    let wrap;\n    tree.match({tag: 'div'}, node => {\n        if (node.attrs && node.attrs.wrap) {\n            wrap = node;\n        }\n        return node;\n    });\n\n    return {\n        slideNode: slide,\n        wrapNode: wrap\n    };\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/utils.js",
    "content": "exports.mergeAttrs = (attrs1, attrs2 = {}) => {\n    for (let i in attrs2) {\n        switch (i) {\n            case 'class':\n                attrs1[i] = attrs1[i] ? [attrs1[i], attrs2[i]].join(' ') : attrs2[i];\n                break;\n            case 'style':\n                attrs1[i] = attrs1[i] ? [attrs1[i], attrs2[i]].join(';') : attrs2[i];\n                break;\n            default:\n                attrs1[i] = attrs2[i];\n        }\n    }\n    return attrs1;\n};\n"
  },
  {
    "path": "packages/nodeppt-parser/lib/yaml-parser.js",
    "content": "const yaml = require('js-yaml');\nfunction getSettings(str) {\n    const settings = yaml.load(str);\n    const pluginSettings = {};\n    if (settings.plugins && Array.isArray(settings.plugins)) {\n        settings.plugins = settings.plugins.map(p => {\n            if (typeof p === 'string') {\n                return p;\n            } else if (typeof p === 'object') {\n                const key = Object.keys(p)[0];\n                pluginSettings[key] = p[key];\n                return key;\n            }\n        });\n    }\n    settings.pluginsOptions = pluginSettings;\n    return settings;\n}\n\nmodule.exports = getSettings;\n"
  },
  {
    "path": "packages/nodeppt-parser/package.json",
    "content": "{\n    \"name\": \"nodeppt-parser\",\n    \"version\": \"2.2.1\",\n    \"description\": \"\",\n    \"main\": \"index.js\",\n    \"scripts\": {\n        \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n    },\n    \"keywords\": [\n        \"presentation\",\n        \"powerpoint\",\n        \"slideshow\",\n        \"keynote\",\n        \"ppt\",\n        \"slide\",\n        \"revealjs\",\n        \"impressjs\",\n        \"markdown-it\",\n        \"posthtml\",\n        \"webpack\",\n        \"nodeppt\",\n        \"markdown\"\n    ],\n    \"author\": {\n        \"name\": \"Theo Wang\",\n        \"email\": \"ksky521@gmail.com\"\n    },\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"git://github.com/ksky521/nodeppt\"\n    },\n    \"license\": \"MIT\",\n    \"dependencies\": {\n        \"ejs\": \"^3.0.1\",\n        \"fs-extra\": \"^8.1.0\",\n        \"js-yaml\": \"^3.12.1\",\n        \"loader-utils\": \"^1.2.3\",\n        \"lodash.defaultsdeep\": \"^4.6.0\",\n        \"markdown-it\": \"^10.0.0\",\n        \"markdown-it-br\": \"^1.0.0\",\n        \"markdown-it-jsx\": \"1.1.0\",\n        \"@ksky521/markdown-it-katex\":\"^2.1.3\",\n        \"markdown-it-span\": \"^1.0.0\",\n        \"markdown-it-sup\": \"^1.0.0\",\n        \"posthtml\": \"^0.12.0\",\n        \"posthtml-parser\": \"^0.4.1\",\n        \"posthtml-render\": \"^1.1.4\",\n        \"prismjs\": \"^1.15.0\"\n    },\n    \"gitHead\": \"9fba34ba1a8cc3ab149c2447c313e25b1e25093e\"\n}\n"
  },
  {
    "path": "packages/nodeppt-parser/template/index.ejs",
    "content": "<!--\n    Powered By nodeppt - This is probably the best web presentation tool so far!\n    date: <%= date %>\n-->\n<!doctype html>\n<html>\n<head>\n    <meta charset=\"UTF-8\">\n    <title><%= title %> - By <%= speaker %></title>\n    <link rel=\"stylesheet\" href=\"https://cdn.staticfile.org/font-awesome/4.7.0/css/font-awesome.min.css\">\n    <link rel=\"stylesheet\" href=\"https://cdn.staticfile.org/prism/1.15.0/themes/prism.min.css\">\n    <link rel=\"stylesheet\" href=\"https://cdn.staticfile.org/KaTeX/0.10.0-rc.1/katex.min.css\">\n    <% if (hasOwnProperty('prismTheme') && ['dark', 'coy', 'funky', 'okaidia', 'tomorrow', 'solarizedlight', 'twilight'].includes(prismTheme)) { %>\n        <link rel=\"stylesheet\" href=\"https://cdn.staticfile.org/prism/1.15.0/themes/prism-<%= prismTheme %>.min.css\">\n    <% } %>\n\n    <% if (hasOwnProperty('css') && css.length) { %>\n        <% for (var i = 0;i<css.length;i++) { %>\n        <link rel=\"stylesheet\" href=\"<%= css[i] %>\">\n        <% } %>\n    <% } %>\n\n    <% if (hasOwnProperty('plugins') && plugins && plugins.indexOf && ~plugins.indexOf('katex')) { %>\n        <link rel=\"stylesheet\" href=\"https://cdn.staticfile.org/KaTeX/0.5.1/katex.min.css\">\n    <% } %>\n</head>\n<body>\n<div>\n    <article id=\"webslides\">\n        <%- content %>\n    </article>\n</div>\n<% if (hasOwnProperty('plugins') && plugins && plugins.indexOf && ~plugins.indexOf('echarts')) { %>\n    <script src=\"https://cdn.staticfile.org/echarts/4.8.0/echarts.min.js\"></script>\n<% } %>\n<% if (hasOwnProperty('plugins') && plugins && plugins.indexOf && ~plugins.indexOf('mermaid')) { %>\n    <script src=\"https://cdn.staticfile.org/mermaid/8.5.2/mermaid.min.js\"></script>\n    <script>mermaid.startOnLoad = false;</script>\n<% } %>\n\n<% if (hasOwnProperty('js') && js.length) { %>\n    <% for (var i = 0;i<js.length;i++) { %>\n    <script src=\"<%= js[i] %>\"></script>\n    <% } %>\n<% } %>\n\n<script>\n<% if(hasOwnProperty('pluginsOptions') && typeof pluginsOptions==='object' ) { %>\n    window.pluginsOptions = <%- JSON.stringify(pluginsOptions) %>\n<% }else { %>\n    window.pluginsOptions = {}\n<% } %>\n\n<% if(hasOwnProperty('webslidesOptions') && typeof webslidesOptions==='object' ) { %>\n    window.webslidesOptions = <%- JSON.stringify(webslidesOptions) %>\n<% }else { %>\n    window.webslidesOptions = {}\n<% } %>\n\ndocument.addEventListener('DOMContentLoaded', () => {\n    let isPrintMode = false;\n    if(~location.search.indexOf('print-pdf')){\n        isPrintMode = true;\n        WebSlides.registerPlugin('scroll', function(){});\n    }\n    const wsOptions = {\n        loop: false\n    };\n    if(window.webslidesOptions){\n        for (let i in webslidesOptions){\n            if(webslidesOptions.hasOwnProperty(i)){\n                wsOptions[i] = webslidesOptions[i];\n            }\n        }\n    }\n    const ws = new WebSlides(wsOptions)\n\n    window.wsInstance = ws;\n    if(isPrintMode){\n        ws.slides.forEach(s=>s.show())\n    }\n}, false)\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "packages/nodeppt-serve/.npmignore",
    "content": "__tests__\n__mocks__\npackage-lock.json\nyarn.lock\n"
  },
  {
    "path": "packages/nodeppt-serve/PluginAPI.js",
    "content": "/**\n * @file pluginAPI from vue cli\n */\nconst path = require('path');\n\nclass PluginAPI {\n    constructor(id, service) {\n        this.id = id;\n        this.service = service;\n    }\n    getEntry() {\n        return this.service.entry;\n    }\n    getEntryName() {\n        return path.parse(this.service.entry).name;\n    }\n    getNodepptOptions() {\n        return this.service.nodepptOptions;\n    }\n    /**\n     * 获取当前工作目录\n     * @return {string} 返回工作目录\n     */\n    getCwd() {\n        return this.service.context;\n    }\n    /**\n     * 获取配置，不传入则返回全部\n     * @param {string} name\n     */\n    getProjectOptions(name) {\n        const opts = this.service.projectOptions;\n        if (name) {\n            return opts[name];\n        }\n        return opts;\n    }\n    /**\n     * Resolve path for a project.\n     *\n     * @param {string} p - Relative path from project root\n     * @return {string} The resolved absolute path.\n     */\n    resolve(p) {\n        return path.resolve(this.service.context, p);\n    }\n\n    registerCommand(name, opts, fn) {\n        if (typeof opts === 'function') {\n            fn = opts;\n            opts = null;\n        }\n        this.service.commands[name] = {fn, opts: opts || {}};\n    }\n\n    chainWebpack(fn) {\n        this.service.webpackChainFns.push(fn);\n    }\n\n    configureWebpack(fn) {\n        this.service.webpackRawConfigFns.push(fn);\n    }\n\n    configureDevServer(fn) {\n        this.service.devServerConfigFns.push(fn);\n    }\n\n    resolveWebpackConfig(chainableConfig) {\n        return this.service.resolveWebpackConfig(chainableConfig);\n    }\n\n    /**\n     * Resolve an intermediate chainable webpack config instance, which can be\n     * further tweaked before generating the final raw webpack config.\n     * You can call this multiple times to generate different branches of the\n     * base webpack config.\n     * See https://github.com/mozilla-neutrino/webpack-chain\n     *\n     * @return {ChainableWebpackConfig}\n     */\n    resolveChainableWebpackConfig() {\n        return this.service.resolveChainableWebpackConfig();\n    }\n}\n\nmodule.exports = PluginAPI;\n"
  },
  {
    "path": "packages/nodeppt-serve/Service.js",
    "content": "/**\n * @file 简版Service\n */\nconst path = require('path');\nconst fs = require('fs');\nconst Config = require('webpack-chain');\nconst merge = require('webpack-merge');\nconst PluginAPI = require('./PluginAPI');\nconst {error, isPlugin, chalk, warn, getDebugLogger} = require('nodeppt-shared-utils');\nconst defaultsDeep = require('lodash.defaultsdeep');\nconst defaults = require('./options');\nconst debug = getDebugLogger('Service', require('./package.json').name);\nconst configFileName = 'nodeppt.config.js';\n\nmodule.exports = class Service {\n    constructor(context, entry, {plugins, pkg, useBuiltIn, nodepptOptions} = {}) {\n        this.initialized = false;\n        this.entry = path.resolve(context, entry);\n        // 这是nodeppt 传过来的配置，主要用version。。。\n        this.nodepptOptions = nodepptOptions;\n        this.pkg = pkg || {};\n        this.pkgContext = context;\n        this.commands = {};\n        this.context = context;\n\n        this.webpackChainFns = [];\n        this.webpackRawConfigFns = [];\n        this.devServerConfigFns = [];\n        this.plugins = this.resolvePlugins(plugins, useBuiltIn);\n        this.modes = this.plugins.reduce((modes, {apply: {defaultModes}}) => {\n            return Object.assign(modes, defaultModes);\n        }, {});\n    }\n    init(mode) {\n        if (this.initialized) {\n            return;\n        }\n        this.initialized = true;\n        this.mode = mode;\n        const userOptions = this.loadUserOptions();\n        const projectOptions = (this.projectOptions = defaultsDeep(userOptions, defaults()));\n        debug(projectOptions);\n        // apply plugins.\n        this.plugins.forEach(({id, apply}) => {\n            if (id.indexOf('markdown') === 0 || id.indexOf('posthtml') === 0) {\n                // 如果是以markdown 和posthtml开头的，则不是service插件，而是markdown-it和posthtml插件\n                return;\n            }\n            apply(new PluginAPI(id, this), projectOptions);\n        });\n        if (projectOptions.chainWebpack) {\n            this.webpackChainFns.push(projectOptions.chainWebpack);\n        }\n        if (projectOptions.configureWebpack) {\n            this.webpackRawConfigFns.push(projectOptions.configureWebpack);\n        }\n    }\n    loadUserOptions() {\n        // hulk.config.js\n        let fileConfig;\n        let resolved = {};\n        const configPath = path.resolve(this.context, configFileName);\n        if (fs.existsSync(configPath)) {\n            try {\n                fileConfig = require(configPath);\n                if (!fileConfig || typeof fileConfig !== 'object') {\n                    error(`Error loading ${chalk.bold(configFileName)}: should export an object.`);\n                    fileConfig = null;\n                }\n            } catch (e) {\n                error(`Error loading ${chalk.bold(configFileName)}:`);\n                throw e;\n            }\n        }\n\n        if (fileConfig) {\n            resolved = fileConfig;\n        }\n\n        // normalize some options\n        ensureSlash(resolved, 'baseUrl');\n        if (typeof resolved.baseUrl === 'string') {\n            resolved.baseUrl = resolved.baseUrl.replace(/^\\.\\//, '');\n        }\n        removeSlash(resolved, 'outputDir');\n\n        return resolved;\n    }\n    async run(name, args = {}, rawArgv = []) {\n        // debugger;\n        const mode = args.mode || (name === 'build' && args.watch ? 'development' : this.modes[name]);\n        process.env.NODE_ENV = mode || 'development';\n\n        // load env variables, load user config, apply plugins\n        this.init(mode);\n\n        args._ = args._ || [];\n        let command = this.commands[name];\n        if (!command && name) {\n            error(`command \"${name}\" does not exist.`);\n            process.exit(1);\n        }\n        if (!command || args.help) {\n            command = this.commands.help;\n        } else {\n            args._.shift(); // remove command itself\n            rawArgv.shift();\n        }\n        const {fn} = command;\n        return fn(args, rawArgv);\n    }\n    resolvePlugins(inlinePlugins, useBuiltIn) {\n        const idToPlugin = (id) => ({\n            id: id.replace(/^.\\//, 'built-in:'),\n            apply: require(id),\n        });\n\n        let plugins;\n\n        const builtInPlugins = [\n            './commands/serve',\n            './commands/build',\n            // config plugins are order sensitive\n            './config/base',\n            './config/css',\n            './config/dev',\n            './config/prod',\n            './config/app',\n        ].map(idToPlugin);\n\n        if (inlinePlugins) {\n            plugins = useBuiltIn !== false ? builtInPlugins.concat(inlinePlugins) : inlinePlugins;\n        } else {\n            const projectPlugins = Object.keys(this.pkg.devDependencies || {})\n                .concat(Object.keys(this.pkg.dependencies || {}))\n                .filter(isPlugin)\n                .map(idToPlugin);\n            plugins = builtInPlugins.concat(projectPlugins);\n        }\n\n        return plugins;\n    }\n    resolveChainableWebpackConfig() {\n        const chainableConfig = new Config();\n        // apply chains\n        this.webpackChainFns.forEach((fn) => fn(chainableConfig));\n        return chainableConfig;\n    }\n    resolveWebpackConfig(chainableConfig = this.resolveChainableWebpackConfig()) {\n        if (!this.initialized) {\n            throw new Error('Service must call init() before calling resolveWebpackConfig().');\n        }\n        // get raw config\n        let config = chainableConfig.toConfig();\n        const original = config;\n        // apply raw config fns\n        this.webpackRawConfigFns.forEach((fn) => {\n            if (typeof fn === 'function') {\n                // function with optional return value\n                const res = fn(config);\n                if (res) {\n                    config = merge(config, res);\n                }\n            } else if (fn) {\n                // merge literal values\n                config = merge(config, fn);\n            }\n        });\n\n        // #2206 If config is merged by merge-webpack, it discards the __ruleNames\n        // information injected by webpack-chain. Restore the info so that\n        // vue inspect works properly.\n        if (config !== original) {\n            cloneRuleNames(config.module && config.module.rules, original.module && original.module.rules);\n        }\n\n        return config;\n    }\n};\n\nfunction cloneRuleNames(to, from) {\n    if (!to || !from) {\n        return;\n    }\n    from.forEach((r, i) => {\n        if (to[i]) {\n            Object.defineProperty(to[i], '__ruleNames', {\n                value: r.__ruleNames,\n            });\n            cloneRuleNames(to[i].oneOf, r.oneOf);\n        }\n    });\n}\n\nfunction ensureSlash(config, key) {\n    let val = config[key];\n    if (typeof val === 'string') {\n        if (!/^https?:/.test(val)) {\n            val = val.replace(/^([^/.])/, '/$1');\n        }\n        config[key] = val.replace(/([^/])$/, '$1/');\n    }\n}\n\nfunction removeSlash(config, key) {\n    if (typeof config[key] === 'string') {\n        config[key] = config[key].replace(/\\/$/g, '');\n    }\n}\n"
  },
  {
    "path": "packages/nodeppt-serve/commands/build.js",
    "content": "/**\n * @file build 主要内容\n */\nconst {info} = require('nodeppt-shared-utils');\nconst path = require('path');\nmodule.exports = (api, options) => {\n    api.registerCommand('build', {}, async function serve(args) {\n        info('Build ...');\n        const context = process.cwd();\n        const {dest} = args;\n\n        process.env.NODE_ENV = 'production';\n        const webpack = require('webpack');\n        // resolve webpack config\n        const webpackConfig = api.resolveWebpackConfig();\n        webpackConfig.mode = 'production';\n\n        if (!args.map) {\n            delete webpackConfig.devtool; // = null;\n        }\n        webpackConfig.output.publicPath = './';\n\n        if (dest) {\n            const targetDir = path.resolve(context, dest || options.outputDir);\n            webpackConfig.output.path = targetDir;\n        }\n        return new Promise((resolve, reject) => {\n            webpack(webpackConfig, err => {\n                if (err) {\n                    return reject(err);\n                }\n                resolve();\n            });\n        });\n    });\n};\n\nmodule.exports.defaultModes = {\n    build: 'production'\n};\n"
  },
  {
    "path": "packages/nodeppt-serve/commands/serve.js",
    "content": "/**\n * 部分代码来自 vue cli\n * @file serve 主要内容\n */\nconst {info, prepareUrls, getLatestVersion, newVersionLog} = require('nodeppt-shared-utils');\nconst semver = require('semver');\n\nlet newVersion = 0;\n\nconst defaults = {\n    host: '0.0.0.0',\n    port: 8080,\n    https: false\n};\n\nmodule.exports = (api, options) => {\n    api.registerCommand(\n        'serve',\n        {\n            description: 'nodeppt dev server',\n            usage: 'nodeppt serve [options] [entry]',\n            options: {\n                '--host': `specify host (default: ${defaults.host})`,\n                '--port': `specify port (default: ${defaults.port})`,\n                '--https': `use https (default: ${defaults.https})`,\n                '--public': 'specify the public network URL for the HMR client'\n            }\n        },\n        async function serve(args) {\n            info('Starting development server...');\n\n            const currentVersion = args.version;\n            // 获取版本更新\n            getLatestVersion()\n                .then(latest => {\n                    if (semver.lt(currentVersion, latest)) {\n                        newVersion = latest;\n                    }\n                })\n                .catch(e => {});\n\n            const url = require('url');\n            const path = require('path');\n            const chalk = require('chalk');\n            const webpack = require('webpack');\n\n            const WebpackDevServer = require('webpack-dev-server');\n            const portfinder = require('portfinder');\n\n            // resolve webpack config\n            const webpackConfig = api.resolveWebpackConfig();\n\n            // in webpck config\n            const projectDevServerOptions = Object.assign(webpackConfig.devServer || {}, options.devServer);\n            // entry arg\n            const entry = args._[0];\n            if (entry) {\n                webpackConfig.entry = {\n                    app: api.resolve(entry)\n                };\n            }\n\n            // resolve server options\n            const useHttps = args.https || projectDevServerOptions.https || defaults.https;\n            const protocol = useHttps ? 'https' : 'http';\n            const host = args.host || process.env.HOST || projectDevServerOptions.host || defaults.host;\n            portfinder.basePort = args.port || process.env.PORT || projectDevServerOptions.port || defaults.port;\n            const port = await portfinder.getPortPromise();\n            const rawPublicUrl = args.public || projectDevServerOptions.public;\n\n            const publicUrl = rawPublicUrl\n                ? /^[a-zA-Z]+:\\/\\//.test(rawPublicUrl)\n                    ? rawPublicUrl\n                    : `${protocol}://${rawPublicUrl}`\n                : null;\n            const urls = prepareUrls(protocol, host, port, options.baseUrl);\n            // inject dev & hot-reload middleware entries\n            const sockjsUrl = publicUrl\n                ? `?${publicUrl}/sockjs-node`\n                : `?${url.format({\n                      protocol,\n                      port,\n                      hostname: urls.lanUrlForConfig || 'localhost',\n                      pathname: '/sockjs-node'\n                  })}`;\n\n            const devClients = [\n                // dev server client\n                require.resolve('../template/reload.js'),\n                require.resolve('webpack-dev-server/client') + sockjsUrl\n                // hmr client\n            ];\n            // inject dev/hot client\n            addDevClientToEntry(webpackConfig, devClients);\n\n            // create compiler\n            const compiler = webpack(webpackConfig);\n\n            // create server\n            const server = new WebpackDevServer(\n                compiler,\n                Object.assign(\n                    {\n                        clientLogLevel: 'none',\n                        historyApiFallback: {\n                            disableDotRule: true,\n                            rewrites: [{from: /./, to: path.posix.join(options.baseUrl, 'index.html')}]\n                        },\n                        contentBase: api.resolve('public'),\n                        watchContentBase: true,\n                        hot: true,\n                        quiet: true,\n                        compress: false,\n                        publicPath: options.baseUrl,\n                        overlay: false\n                    },\n                    projectDevServerOptions,\n                    {\n                        https: useHttps,\n                        before(app, server) {\n                            // allow other plugins to register middlewares, e.g. PWA\n                            api.service.devServerConfigFns.forEach(fn => fn(app, server));\n                            // apply in project middlewares\n                            projectDevServerOptions.before && projectDevServerOptions.before(app, server);\n                        }\n                    }\n                )\n            );\n\n            ['SIGINT', 'SIGTERM'].forEach(signal => {\n                process.on(signal, () => {\n                    server.close(() => {\n                        if (newVersion) {\n                            newVersionLog(currentVersion, newVersion);\n                        }\n                        process.exit(0);\n                    });\n                });\n            });\n\n            return new Promise((resolve, reject) => {\n                // log instructions & open browser on first compilation complete\n                let isFirstCompile = true;\n                compiler.hooks.done.tap('nodeppt-serve', stats => {\n                    if (stats.hasErrors()) {\n                        return;\n                    }\n\n                    console.log();\n                    console.log('  NodePPT running at:');\n                    const networkUrl = publicUrl ? publicUrl.replace(/([^/])$/, '$1/') : urls.lanUrlForTerminal;\n                    console.log(`  - Url: ${chalk.cyan(networkUrl)}`);\n                    console.log(`  - Speaker Mode: ${chalk.cyan(networkUrl + '?mode=speaker')}`);\n                    console.log();\n\n                    if (isFirstCompile) {\n                        isFirstCompile = false;\n\n                        // resolve returned Promise\n                        // so other commands can do api.service.run('serve').then(...)\n                        resolve({\n                            server,\n                            url: urls.localUrlForBrowser\n                        });\n                    } else {\n                    }\n                });\n\n                server.listen(port, host, err => {\n                    if (err) {\n                        reject(err);\n                    }\n                });\n            });\n        }\n    );\n};\n\nfunction addDevClientToEntry(config, devClient) {\n    const {entry} = config;\n    if (typeof entry === 'object' && !Array.isArray(entry)) {\n        Object.keys(entry).forEach(key => {\n            entry[key] = devClient.concat(entry[key]);\n        });\n    } else if (typeof entry === 'function') {\n        config.entry = entry(devClient);\n    } else {\n        config.entry = devClient.concat(entry);\n    }\n}\n\nmodule.exports.defaultModes = {\n    serve: 'development'\n};\n"
  },
  {
    "path": "packages/nodeppt-serve/config/app.js",
    "content": "/**\n * @file app\n */\nconst fs = require('fs');\nconst path = require('path');\n\n// ensure the filename passed to html-webpack-plugin is a relative path\n// because it cannot correctly handle absolute paths\nfunction ensureRelative(outputDir, p) {\n    if (path.isAbsolute(p)) {\n        return path.relative(outputDir, p);\n    } else {\n        return p;\n    }\n}\n\nmodule.exports = (api, options) => {\n    api.chainWebpack(webpackConfig => {\n        const isProd = process.env.NODE_ENV === 'production';\n        const outputDir = api.resolve(options.outputDir);\n        // code splitting\n        if (isProd) {\n            webpackConfig.optimization.splitChunks({\n                cacheGroups: {\n                    vendors: {\n                        name: 'chunk-vendors',\n                        test: /[\\\\/]node_modules[\\\\/]/,\n                        priority: -10,\n                        chunks: 'initial'\n                    },\n                    common: {\n                        name: 'chunk-common',\n                        minChunks: 2,\n                        priority: -20,\n                        chunks: 'initial',\n                        reuseExistingChunk: true\n                    }\n                }\n            });\n        }\n\n        // HTML plugin\n        // #1669 html-webpack-plugin's default sort uses toposort which cannot\n        // handle cyclic deps in certain cases. Monkey patch it to handle the case\n        // before we can upgrade to its 4.0 version (incompatible with preload atm)\n        const chunkSorters = require('html-webpack-plugin/lib/chunksorter');\n        const depSort = chunkSorters.dependency;\n        chunkSorters.auto = chunkSorters.dependency = (chunks, ...args) => {\n            try {\n                return depSort(chunks, ...args);\n            } catch (e) {\n                // fallback to a manual sort if that happens...\n                return chunks.sort((a, b) => {\n                    // make sure user entry is loaded last so user CSS can override\n                    // vendor CSS\n                    if (a.id === 'app') {\n                        return 1;\n                    } else if (b.id === 'app') {\n                        return -1;\n                    } else if (a.entry !== b.entry) {\n                        return b.entry ? -1 : 1;\n                    }\n                    return 0;\n                });\n            }\n        };\n\n        const htmlOptions = {\n            templateParameters: (compilation, assets, pluginOptions) => {\n                // enhance html-webpack-plugin's built in template params\n                let stats;\n                return Object.assign({\n                    // make stats lazy as it is expensive\n                    get webpack() {\n                        return stats || (stats = compilation.getStats().toJson());\n                    },\n                    compilation: compilation,\n                    webpackConfig: compilation.options,\n                    htmlWebpackPlugin: {\n                        files: assets,\n                        options: pluginOptions\n                    }\n                });\n            }\n        };\n\n        if (isProd) {\n            Object.assign(htmlOptions, {\n                minify: {\n                    removeComments: true,\n                    collapseWhitespace: true,\n                    removeAttributeQuotes: true,\n                    collapseBooleanAttributes: true,\n                    removeScriptTypeAttributes: true\n                    // more options:\n                    // https://github.com/kangax/html-minifier#options-quick-reference\n                }\n            });\n\n            // keep chunk ids stable so async chunks have consistent hash (#1916)\n            webpackConfig.plugin('named-chunks').use(require('webpack/lib/NamedChunksPlugin'), [\n                chunk => {\n                    if (chunk.name) {\n                        return chunk.name;\n                    }\n                    return (\n                        'chunk-' +\n                        Array.from(chunk.modulesIterable, m => {\n                            return m.id;\n                        }).join('_')\n                    );\n                }\n            ]);\n        }\n\n        // resolve HTML file(s)\n        const HTMLPlugin = require('html-webpack-plugin');\n        const multiPageConfig = options.pages;\n        const htmlPath = api.getEntry();\n        const publicCopyIgnore = ['index.html', '.DS_Store'];\n\n        if (!multiPageConfig) {\n            // default, single page setup.\n            htmlOptions.template = htmlPath;\n            if (isProd) {\n                htmlOptions.filename = api.getEntryName() + '.html';\n            }\n            webpackConfig.plugin('html').use(HTMLPlugin, [htmlOptions]);\n        } else {\n            // multi-page setup\n            webpackConfig.entryPoints.clear();\n\n            const pages = Object.keys(multiPageConfig);\n            const normalizePageConfig = c => (typeof c === 'string' ? {entry: c} : c);\n\n            pages.forEach(name => {\n                const {\n                    title,\n                    entry,\n                    template = `public/${name}.html`,\n                    filename = `${name}.html`,\n                    chunks\n                } = normalizePageConfig(multiPageConfig[name]);\n                // inject entry\n                webpackConfig.entry(name).add(api.resolve(entry));\n\n                // resolve page index template\n                const hasDedicatedTemplate = fs.existsSync(api.resolve(template));\n                if (hasDedicatedTemplate) {\n                    publicCopyIgnore.push(template);\n                }\n                const templatePath = hasDedicatedTemplate ? template : htmlPath;\n\n                // inject html plugin for the page\n                const pageHtmlOptions = Object.assign({}, htmlOptions, {\n                    chunks: chunks || ['chunk-vendors', 'chunk-common', name],\n                    template: templatePath,\n                    filename: ensureRelative(outputDir, filename),\n                    title\n                });\n\n                webpackConfig.plugin(`html-${name}`).use(HTMLPlugin, [pageHtmlOptions]);\n            });\n        }\n\n        // copy static assets in public/\n        const publicDir = api.resolve('./public');\n        if (fs.existsSync(publicDir)) {\n            webpackConfig.plugin('copy').use(require('copy-webpack-plugin'), [\n                [\n                    {\n                        from: publicDir,\n                        to: outputDir,\n                        ignore: publicCopyIgnore\n                    }\n                ]\n            ]);\n        }\n    });\n};\n"
  },
  {
    "path": "packages/nodeppt-serve/config/base.js",
    "content": "/**\n * @file base\n */\n// const path = require('path');\nconst webpack = require('webpack');\nconst {transformer, formatter} = require('nodeppt-shared-utils');\nmodule.exports = (api, options) => {\n    const {version} = api.getNodepptOptions();\n    api.chainWebpack((webpackConfig) => {\n        const {getAssetPath, resolveLocal} = require('../lib/utils');\n        const inlineLimit = 4096;\n\n        const genAssetSubPath = (dir) => {\n            return getAssetPath(options, `${dir}/[name]${options.filenameHashing ? '.[hash:8]' : ''}.[ext]`);\n        };\n\n        const genUrlLoaderOptions = (dir) => {\n            return {\n                limit: inlineLimit,\n                // use explicit fallback to avoid regression in url-loader>=1.1.0\n                fallback: {\n                    loader: 'file-loader',\n                    options: {\n                        name: genAssetSubPath(dir),\n                        esModule: false,\n                    },\n                },\n            };\n        };\n        let entryName = 'app';\n        if (api.service.entry) {\n            entryName = api.getEntryName();\n        }\n        webpackConfig\n            .mode('development')\n            .context(api.context)\n            .entry(entryName)\n            .add('./main.js')\n            .end()\n            .output.path(api.resolve(options.outputDir))\n            .filename(`[name]${options.filenameHashing ? '.[hash:8]' : ''}.js`)\n            .publicPath(options.baseUrl);\n\n        webpackConfig.resolve\n            .set('symlinks', false)\n            .extensions.merge(['.js', '.less'])\n            .end()\n            .modules.add('node_modules')\n            .add(api.resolve('node_modules'))\n            .add(resolveLocal('node_modules'))\n            .end();\n\n        webpackConfig.resolveLoader.modules\n            .add('node_modules')\n            .add(api.resolve('node_modules'))\n            .add(resolveLocal('node_modules'));\n        webpackConfig.module\n            .rule('markdown')\n            .test(/\\.(md|markdown)$/)\n            .use('html-loader')\n            .loader(require.resolve('html-loader'))\n            .end()\n            .use('nodeppt-parser')\n            .loader(require.resolve('nodeppt-parser'))\n            .options({plugins: options.plugins, template: options.baseTemplate})\n            .end();\n\n        webpackConfig.module\n            .rule('js')\n            .test(/\\.js$/)\n            .use('babel-loader')\n            .loader(require.resolve('babel-loader'))\n            .options({cacheDirectory: true})\n            .end();\n        // static assets -----------------------------------------------------------\n\n        webpackConfig.module\n            .rule('images')\n            .test(/\\.(png|jpe?g|gif|webp)(\\?.*)?$/)\n            .use('url-loader')\n            .loader(require.resolve('url-loader'))\n            .options(genUrlLoaderOptions('img'));\n\n        // do not base64-inline SVGs.\n        // https://github.com/facebookincubator/create-react-app/pull/1180\n        webpackConfig.module\n            .rule('svg')\n            .test(/\\.(svg)(\\?.*)?$/)\n            .use('file-loader')\n            .loader(require.resolve('file-loader'))\n            .options({\n                name: genAssetSubPath('img'),\n            });\n\n        webpackConfig.module\n            .rule('media')\n            .test(/\\.(mp4|webm|ogg|mp3|wav|flac|aac)(\\?.*)?$/)\n            .use('url-loader')\n            .loader(require.resolve('url-loader'))\n            .options(genUrlLoaderOptions('media'));\n\n        webpackConfig.module\n            .rule('fonts')\n            .test(/\\.(woff2?|eot|ttf|otf)(\\?.*)?$/i)\n            .use('url-loader')\n            .loader(require.resolve('url-loader'))\n            .options(genUrlLoaderOptions('fonts'));\n\n        webpackConfig.plugin('banner').use(\n            new webpack.BannerPlugin({\n                banner: `Created by nodeppt ${version} \\n - Install: npm install -g nodeppt\\n - Github: https://github.com/ksky521/nodeppt`,\n            })\n        );\n\n        webpackConfig.plugin('case-sensitive-paths').use(require('case-sensitive-paths-webpack-plugin'));\n\n        // friendly error plugin displays very confusing errors when webpack\n        // fails to resolve a loader, so we provide custom handlers to improve it\n        webpackConfig.plugin('friendly-errors').use(require('friendly-errors-webpack-plugin'), [\n            {\n                additionalTransformers: [transformer],\n                additionalFormatters: [formatter],\n            },\n        ]);\n\n        webpackConfig.plugin('progress').use(require('webpack/lib/ProgressPlugin'));\n    });\n};\n"
  },
  {
    "path": "packages/nodeppt-serve/config/css.js",
    "content": "/**\n * @file css webpack\n */\n\n// const {findExisting} = require('nodeppt-shared-utils');\nconst getAssetPath = require('../lib/utils').getAssetPath;\nmodule.exports = (api, options) => {\n    api.chainWebpack(webpackConfig => {\n        const isProd = process.env.NODE_ENV === 'production';\n\n        const {modules = false, extract = isProd, sourceMap = false, loaderOptions = {}} = options.css || {};\n\n        const shouldExtract = extract !== false;\n\n        const filename = getAssetPath(\n            options,\n            `css/[name]${options.filenameHashing || isProd ? '.[contenthash:8]' : ''}.css`\n        );\n        const extractOptions = Object.assign(\n            {\n                filename,\n                chunkFilename: filename\n            },\n            extract && typeof extract === 'object' ? extract : {}\n        );\n\n        const cssPublicPath = '../'.repeat(\n            extractOptions.filename.replace(/^\\.[\\/\\\\]/, '').split(/[\\/\\\\]/g).length - 1\n        );\n\n        // if building for production but not extracting CSS, we need to minimize\n        // the embbeded inline CSS as they will not be going through the optimizing\n        // plugin.\n        const needInlineMinification = isProd && !shouldExtract;\n\n        function createCSSRule(lang, test, loader, options) {\n            const baseRule = webpackConfig.module.rule(lang).test(test);\n            // baseRule.exclude.add(/nodeppt/)\n            applyLoaders(baseRule, modules);\n\n            function applyLoaders(rule, modules) {\n                if (shouldExtract) {\n                    rule.use('extract-css-loader')\n                        .loader(require('mini-css-extract-plugin').loader)\n                        .options({\n                            publicPath: cssPublicPath\n                        });\n                } else {\n                    rule.use('style-loader').loader(require.resolve('style-loader'));\n                }\n\n                const cssLoaderOptions = Object.assign(\n                    {\n                        sourceMap,\n                        importLoaders: 1 + (needInlineMinification ? 1 : 0) // stylePostLoader injected by vue-loader\n                    },\n                    loaderOptions.css\n                );\n\n                if (modules) {\n                    const {localIdentName = '[name]_[local]_[hash:base64:5]'} = loaderOptions.css || {};\n                    Object.assign(cssLoaderOptions, {\n                        modules,\n                        localIdentName\n                    });\n                }\n\n                rule.use('css-loader')\n                    .loader(require.resolve('css-loader'))\n                    .options(cssLoaderOptions);\n\n                if (loader) {\n                    rule.use(loader)\n                        .loader(require.resolve(loader))\n                        .options(Object.assign({sourceMap}, options));\n                }\n            }\n        }\n        createCSSRule('css', /\\.css$/);\n        createCSSRule('less', /\\.less$/, 'less-loader', loaderOptions.less);\n\n        // inject CSS extraction plugin\n        if (shouldExtract) {\n            webpackConfig.plugin('extract-css').use(require('mini-css-extract-plugin'), [extractOptions]);\n        }\n    });\n};\n"
  },
  {
    "path": "packages/nodeppt-serve/config/dev.js",
    "content": "/**\n * @file dev webpack\n */\nmodule.exports = (api, options) => {\n    api.chainWebpack(webpackConfig => {\n        if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {\n            webpackConfig.devtool('cheap-module-eval-source-map').output.publicPath(options.baseUrl);\n\n            webpackConfig.plugin('hmr').use(require('webpack/lib/HotModuleReplacementPlugin'));\n\n            // https://github.com/webpack/webpack/issues/6642\n            webpackConfig.output.globalObject('this');\n\n            webpackConfig.plugin('no-emit-on-errors').use(require('webpack/lib/NoEmitOnErrorsPlugin'));\n\n            if (options.devServer.progress !== false) {\n                webpackConfig.plugin('progress').use(require('webpack/lib/ProgressPlugin'));\n            }\n        }\n    });\n};\n"
  },
  {
    "path": "packages/nodeppt-serve/config/prod.js",
    "content": "/**\n * @file prod webpack\n */\nconst OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');\nconst TerserPlugin = require('terser-webpack-plugin');\nmodule.exports = (api, options) => {\n    api.chainWebpack(webpackConfig => {\n        if (process.env.NODE_ENV === 'production') {\n            const getAssetPath = require('../lib/utils').getAssetPath;\n            const filename = getAssetPath(options, `js/[name]${options.filenameHashing ? '.[contenthash:8]' : ''}.js`);\n\n            webpackConfig\n                .mode('production')\n                .devtool(options.productionSourceMap ? 'source-map' : false)\n                .output.filename(filename)\n                .chunkFilename(filename);\n            // 压缩\n            webpackConfig.optimization\n                .minimizer('css')\n                .use(OptimizeCSSAssetsPlugin, [{cssProcessorOptions: {safe: true}}]);\n\n            webpackConfig.optimization.minimizer('js').use(new TerserPlugin());\n\n            // keep module.id stable when vendor modules does not change\n            webpackConfig.plugin('hash-module-ids').use(require('webpack/lib/HashedModuleIdsPlugin'), [\n                {\n                    hashDigest: 'hex'\n                }\n            ]);\n        }\n    });\n};\n"
  },
  {
    "path": "packages/nodeppt-serve/index.js",
    "content": "/**\n * @file hulk -serve\n */\nconst fs = require('fs');\nconst path = require('path');\nconst chalk = require('chalk');\n\nconst globalConfigPlugin = require('./lib/globalConfigPlugin');\nconst {findExisting} = require('nodeppt-shared-utils');\nconst Service = require('./Service');\n\nfunction resolveEntry(entry) {\n    const context = process.cwd();\n\n    entry = entry || findExisting(context, ['main.js', 'index.js']);\n\n    if (!entry) {\n        console.log(chalk.red(`Failed to locate entry file in ${chalk.yellow(context)}.`));\n        console.log(chalk.red('Valid entry file should be one of: main.js, index.js.'));\n        process.exit(1);\n    }\n    if (!fs.existsSync(path.resolve(context, entry))) {\n        console.log(chalk.red(`Entry file ${chalk.yellow(entry)} does not exist.`));\n        process.exit(1);\n    }\n\n    return {\n        context,\n        entry\n    };\n}\nexports.serve = (e, args) => {\n    const {context, entry} = resolveEntry(e);\n    createService(context, entry, {version: args.version || '2.0'}).run('serve', args);\n};\nexports.build = (e, args) => {\n    const {context, entry} = resolveEntry(e);\n    const asLib = args.target && args.target !== 'app';\n    if (asLib) {\n        args.entry = entry;\n    }\n    createService(context, entry, {version: args.version || '2.0'}, asLib).run('build', args);\n};\n\nfunction createService(context, entry, nodepptOptions, asLib, plugins = []) {\n    // console.log(plugins);\n    return new Service(context, entry, {\n        nodepptOptions,\n        plugins: [...plugins, globalConfigPlugin(context, entry, asLib)]\n    });\n}\n"
  },
  {
    "path": "packages/nodeppt-serve/lib/globalConfigPlugin.js",
    "content": "/**\n * @file global config plugin\n */\nconst path = require('path');\nconst {findExisting} = require('nodeppt-shared-utils');\n\nmodule.exports = function createConfigPlugin(context, entry, asLib) {\n    return {\n        id: 'nodeppt-service-global-config',\n        apply: (api, options) => {\n            api.chainWebpack(config => {\n                // entry arg\n                const entry = require.resolve('../template/main.js');\n                config.resolve.alias.set('~entry', path.resolve(context, entry));\n\n                let entryName = 'app';\n                if (api.service.entry) {\n                    entryName = api.getEntryName();\n                }\n                // set entry\n                config\n                    .entry(entryName)\n                    .clear()\n                    .add(entry);\n\n                // disable copy plugin if no public dir\n                if (asLib || !findExisting(context, ['public'])) {\n                    config.plugins.delete('copy');\n                }\n            });\n        }\n    };\n};\n"
  },
  {
    "path": "packages/nodeppt-serve/lib/utils.js",
    "content": "const fs = require('fs');\nconst path = require('path');\nconst {findExisting, chalk} = require('nodeppt-shared-utils');\n\nexports.resolveEntry = entry => {\n    const context = process.cwd();\n\n    entry = entry || findExisting(context, ['README.md', 'readme.md', 'index.md', 'default.md']);\n\n    if (!entry) {\n        console.log(chalk.red(`Failed to locate entry file in ${chalk.yellow(context)}.`));\n        console.log(chalk.red('Valid entry file should be one of: readme.md, index.md, README.md or default.md.'));\n        process.exit(1);\n    }\n    if (typeof entry === 'string' && !fs.existsSync(path.resolve(context, entry))) {\n        console.log(chalk.red(`Entry file ${chalk.yellow(entry)} does not exist.`));\n        process.exit(1);\n    }\n\n    return {\n        context,\n        entry\n    };\n};\n\nexports.getAssetPath = (options, filePath) =>\n    options.assetsDir ? path.posix.join(options.assetsDir, filePath) : filePath;\n\nexports.resolveLocal = function resolveLocal(...args) {\n    return path.join(__dirname, '../../', ...args);\n};\n"
  },
  {
    "path": "packages/nodeppt-serve/options.js",
    "content": "/**\n * @file 默认配置\n */\nmodule.exports = () => ({\n    // project deployment base\n    baseUrl: '/',\n\n    // where to output built files\n    outputDir: 'dist',\n\n    // where to put static assets (js/css/img/font/...)\n    assetsDir: '',\n\n    // filename for index.html (relative to outputDir)\n    indexPath: 'index.html',\n    // 插件，包括 markdown 和 posthtml\n    plugins: [],\n    // chainWebpack: [],\n\n    // whether filename will contain hash part\n    filenameHashing: false,\n\n    // boolean, use full build?\n    runtimeCompiler: false,\n\n    // deps to transpile\n    transpileDependencies: [\n        /* string or regex */\n    ],\n\n    // sourceMap for production build?\n    productionSourceMap: true,\n\n    // use thread-loader for babel & TS in production build\n    // enabled by default if the machine has more than 1 cores\n    parallel: () => {\n        try {\n            return require('os').cpus().length > 1;\n        } catch (e) {\n            return false;\n        }\n    },\n\n    // multi-page config\n    pages: undefined,\n\n    // <script type=\"module\" crossorigin=\"use-credentials\">\n    // #1656, #1867, #2025\n    crossorigin: undefined,\n\n    // subresource integrity\n    integrity: false,\n\n    css: {\n        extract: true\n        // modules: false,\n        // localIdentName: '[name]_[local]_[hash:base64:5]',\n        // sourceMap: false,\n        // loaderOptions: {}\n    },\n\n    devServer: {\n        /*\n      host: '0.0.0.0',\n      port: 8080,\n      https: false,\n      proxy: null, // string | Object\n      before: app => {}\n    */\n    }\n});\n"
  },
  {
    "path": "packages/nodeppt-serve/package.json",
    "content": "{\n    \"name\": \"nodeppt-serve\",\n    \"version\": \"2.2.1\",\n    \"description\": \"\",\n    \"main\": \"index.js\",\n    \"scripts\": {\n        \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n    },\n    \"keywords\": [\n        \"presentation\",\n        \"powerpoint\",\n        \"slideshow\",\n        \"keynote\",\n        \"ppt\",\n        \"slide\",\n        \"revealjs\",\n        \"impressjs\",\n        \"markdown-it\",\n        \"posthtml\",\n        \"webpack\",\n        \"nodeppt\",\n        \"markdown\"\n    ],\n    \"author\": {\n        \"name\": \"Theo Wang\",\n        \"email\": \"ksky521@gmail.com\"\n    },\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"git://github.com/ksky521/nodeppt\"\n    },\n    \"license\": \"MIT\",\n    \"dependencies\": {\n        \"@babel/core\": \"7.8.3\",\n        \"babel-loader\": \"8.0.6\",\n        \"case-sensitive-paths-webpack-plugin\": \"2.3.0\",\n        \"chalk\": \"^3.0.0\",\n        \"commander\": \"^4.1.0\",\n        \"copy-webpack-plugin\": \"~5.1.1\",\n        \"css-loader\": \"3.4.2\",\n        \"file-loader\": \"5.0.2\",\n        \"friendly-errors-webpack-plugin\": \"1.7.0\",\n        \"html-loader\": \"0.5.5\",\n        \"html-webpack-plugin\": \"^4.0.0-beta.3\",\n        \"import-global\": \"^0.1.0\",\n        \"less\": \"^3.10.3\",\n        \"less-loader\": \"~5.0.0\",\n        \"lodash.defaultsdeep\": \"^4.6.0\",\n        \"mini-css-extract-plugin\": \"0.9.0\",\n        \"nodeppt-js\": \"^2.2.1\",\n        \"nodeppt-parser\": \"^2.2.1\",\n        \"nodeppt-shared-utils\": \"^2.2.1\",\n        \"optimize-css-assets-webpack-plugin\": \"^5.0.1\",\n        \"portfinder\": \"^1.0.20\",\n        \"semver\": \"^7.1.1\",\n        \"style-loader\": \"1.1.2\",\n        \"terser-webpack-plugin\": \"^2.3.2\",\n        \"url-loader\": \"3.0.0\",\n        \"webpack\": \"~4.41.2\",\n        \"webpack-chain\": \"~6.3.0\",\n        \"webpack-dev-server\": \"^3.9.0\",\n        \"webpack-merge\": \"~4.2.2\"\n    },\n    \"gitHead\": \"9fba34ba1a8cc3ab149c2447c313e25b1e25093e\"\n}\n"
  },
  {
    "path": "packages/nodeppt-serve/template/main.js",
    "content": "/**\n * 页面的 main.js\n */\nimport Slide from 'nodeppt-js';\nif (typeof window === 'object' && Array.isArray(window.WSPlugins_)) {\n    /* eslint-disable fecs-camelcase,no-undef */\n    WSPlugins_.forEach(({id, apply}) => {\n        /* eslint-enable fecs-camelcase,no-undef */\n        Slide.registerPlugin(id, apply);\n    });\n}\n\nwindow.WebSlides = Slide;\n"
  },
  {
    "path": "packages/nodeppt-serve/template/reload.js",
    "content": "if (module.hot) {\n    var hotEmitter = require('webpack/hot/emitter');\n    hotEmitter.on('webpackHotUpdate', currentHash => {\n        location.reload();\n    });\n} else {\n    throw new Error('[HMR] Hot Module Replacement is disabled.');\n}\n"
  },
  {
    "path": "packages/nodeppt-shared-utils/.npmignore",
    "content": "__tests__\n__mocks__\npackage-lock.json\nyarn.lock\n"
  },
  {
    "path": "packages/nodeppt-shared-utils/index.js",
    "content": "/**\n * @file 工具函数导出\n */\n\n[\n    'logger',\n    'spinner',\n    'get-debug',\n    'eval',\n    'path',\n    'new-version-log',\n    'download-repo',\n    'git-user',\n    'get-latest-version',\n    'webpack-error',\n    'prepare-urls',\n    'find-existing'\n].forEach(m => {\n    Object.assign(exports, require(`./lib/${m}`));\n});\n\nexports.chalk = require('chalk');\n"
  },
  {
    "path": "packages/nodeppt-shared-utils/lib/download-repo.js",
    "content": "/**\n * @file 下载 icon repo\n */\nconst gitclone = require('git-clone');\nconst rm = require('fs-extra').removeSync;\nconst debug = require('./get-debug').getDebugLogger('download-repo');\n\nconst {getGitUser} = require('./git-user');\n\nexports.downloadRepo = (repo, dest, opts = {}, fn) => {\n    repo = normalize(repo, opts);\n    const {url, checkout} = repo;\n\n    gitclone(url, dest, {checkout, shallow: checkout === 'master'}, err => {\n        if (!err) {\n            rm(`${dest}/.git`);\n            fn();\n        } else {\n            fn(err);\n            debug(err, url, dest, checkout);\n        }\n    });\n};\nfunction normalize(repo, opts) {\n    // 公司名/目录名/repo#分支\n    const regex = /^(?:(github|gitlab|bitbucket|coding)\\:)?(?:([^\\/]+)\\/)?([^#]+)(?:#(.+))?$/;\n    const useHttps = opts.https || false;\n\n    const match = regex.exec(repo);\n    if (!match) {\n        return {\n            url: repo,\n            checkout: 'master'\n        };\n    }\n\n    const [m, source = 'github', product = 'ksky521', repoName, checkout = 'master'] = match;\n    let url = repo;\n    switch (source) {\n        case 'github':\n        case 'gitlab':\n        case 'bitbucket':\n            if (useHttps) {\n                // https://github.com/vuejs-templates/pwa.git\n                url = `https://${source}.com/${product}/${repoName}.git`;\n            } else {\n                // git@github.com:vuejs-templates/pwa.git\n                url = `git@${source}.com:${product}/${repoName}.git`;\n            }\n            break;\n        case 'coding':\n            if (useHttps) {\n                url = `https://git.coding.net/${product}/${repoName}.git`;\n            } else {\n                url = `git@git.coding.net:${product}/${repoName}.git`;\n            }\n            break;\n    }\n    return {\n        url,\n        checkout\n    };\n}\n"
  },
  {
    "path": "packages/nodeppt-shared-utils/lib/eval.js",
    "content": "/**\n * @file 用于计算判断条件\n */\n\nconst chalk = require('chalk');\n\nexports.evaluate = (exp, data) => {\n    /* eslint-disable no-new-func */\n    const fn = new Function('data', 'with (data) { return ' + exp + '}');\n    try {\n        return fn(data);\n    } catch (e) {\n        console.error(chalk.red('Error when evaluating filter condition: ' + exp));\n    }\n};\n"
  },
  {
    "path": "packages/nodeppt-shared-utils/lib/find-existing.js",
    "content": "/**\n * @file 查找存在的文件\n */\nconst fs = require('fs');\nconst path = require('path');\n\nexports.findExisting = (context, files) => {\n    for (const file of files) {\n        if (fs.existsSync(path.join(context, file))) {\n            return file;\n        }\n    }\n};\n"
  },
  {
    "path": "packages/nodeppt-shared-utils/lib/get-debug.js",
    "content": "/**\n * @file get debug\n */\nconst {name} = require('../package.json');\nconst debug = require('debug');\nexports.getDebugLogger = (ns, scope = name) => {\n    const ms = [scope, ns].filter(v => v).join(':');\n    return debug(ms);\n};\n"
  },
  {
    "path": "packages/nodeppt-shared-utils/lib/get-latest-version.js",
    "content": "/**\n * @file check version\n */\n\nconst request = require('request');\nconst debug = require('./get-debug').getDebugLogger('get-lastest-version');\n\n// 获取最新版本\nfunction getLatestVersion(name = 'nodeppt', registry = 'https://registry.npmjs.org') {\n    return new Promise(resolve => {\n        request(\n            {\n                url: `${registry}/${name}`,\n                timeout: 3000\n            },\n            (err, res, body) => {\n                if (!err && res.statusCode === 200) {\n                    const latest = JSON.parse(body)['dist-tags'].latest;\n                    debug(latest);\n                    resolve(latest);\n                } else {\n                    debug(err, `${registry}/${name}`);\n                    resolve('0.0.0');\n                }\n            }\n        );\n    });\n}\nexports.getLatestVersion = getLatestVersion;\n"
  },
  {
    "path": "packages/nodeppt-shared-utils/lib/git-user.js",
    "content": "/**\n * @file 获取 git 用户名和邮箱\n */\n\nconst exec = require('child_process').execSync;\n\nexports.getGitUser = () => {\n    let name;\n    let email;\n\n    try {\n        name = exec('git config --get user.name');\n        email = exec('git config --get user.email');\n        email = email.toString().trim();\n        name = JSON.stringify(name.toString().trim()).slice(1, -1);\n        const t = email && ' <' + email.toString().trim() + '>';\n        return {\n            name,\n            email,\n            author: (name || '') + (t || '')\n        };\n    } catch (e) {\n        return {};\n    }\n};\n"
  },
  {
    "path": "packages/nodeppt-shared-utils/lib/logger.js",
    "content": "/**\n * @file logger 封装\n */\n\nconst chalk = require('chalk');\nconst readline = require('readline');\nconst padStart = require('string.prototype.padstart');\nconst EventEmitter = require('events');\nconst logger = console.log;\nconst error = console.error;\nconst warn = console.warn;\nexports.events = new EventEmitter();\n\nconst format = (label, msg) => {\n    return msg\n        .split('\\n')\n        .map((line, i) => {\n            return i === 0 ? `${label} ${line}` : padStart(line, chalk.reset(label).length);\n        })\n        .join('\\n');\n};\n\nconst chalkTag = msg => chalk.bgBlackBright.white.dim(` ${msg} `);\n\nexports.log = (msg = '', tag = null) => {\n    tag ? logger(format(chalkTag(tag), msg)) : logger(msg);\n};\n\nexports.info = (msg, tag = null) => {\n    logger(format(chalk.bgBlue.black(' INFO ') + (tag ? chalkTag(tag) : ''), msg));\n};\n\nexports.done = (msg, tag = null) => {\n    logger(format(chalk.bgGreen.black(' DONE ') + (tag ? chalkTag(tag) : ''), msg));\n};\n\nexports.warn = (msg, tag = null) => {\n    warn(format(chalk.bgYellow.black(' WARN ') + (tag ? chalkTag(tag) : ''), chalk.yellow(msg)));\n};\n\nexports.error = (msg, tag = null) => {\n    error(format(chalk.bgRed(' ERROR ') + (tag ? chalkTag(tag) : ''), chalk.red(msg)));\n    if (msg instanceof Error) {\n        error(msg.stack);\n    }\n};\nexports.line = msg => {\n    logger();\n    msg ? logger('─'.repeat(20) + msg + '─'.repeat(20)) : logger('─'.repeat(45));\n    logger();\n};\nexports.success = (msg, tag) => {\n    if (typeof tag !== 'string') {\n        tag = process.platform === 'win32' ? '√' : '✔';\n    }\n\n    tag ? logger(format(`${chalk.green(tag)} `, msg)) : logger(msg);\n};\n\nexports.clearConsole = () => {\n    if (process.stdout.isTTY) {\n        const blank = '\\n'.repeat(process.stdout.rows);\n        logger(blank);\n        readline.cursorTo(process.stdout, 0, 0);\n        readline.clearScreenDown(process.stdout);\n    }\n};\n"
  },
  {
    "path": "packages/nodeppt-shared-utils/lib/new-version-log.js",
    "content": "const chalk = require('chalk');\nconst boxen = require('boxen');\nexports.newVersionLog = (currentVersion, newVersion) => {\n    if (newVersion) {\n        const message = `Update available ${chalk.dim(currentVersion)} → ${chalk.green(newVersion)}\nRun ${chalk.cyan('npm i -g nodeppt')} to update`;\n\n        console.error(\n            boxen(message, {\n                padding: 1,\n                margin: 1,\n                align: 'center',\n                borderColor: 'yellow',\n                borderStyle: 'round'\n            })\n        );\n    }\n};\n"
  },
  {
    "path": "packages/nodeppt-shared-utils/lib/path.js",
    "content": "/**\n * @file 跟路径相关\n */\nconst path = require('path');\n\nexports.isLocalPath = templatePath => {\n    return /^[./]|(^[a-zA-Z]:)/.test(templatePath);\n};\n\nexports.getTemplatePath = templatePath => {\n    const cwd = process.cwd();\n    return path.isAbsolute(templatePath) ? templatePath : path.normalize(path.join(cwd, templatePath));\n};\n"
  },
  {
    "path": "packages/nodeppt-shared-utils/lib/plugin.js",
    "content": "/**\n * @file plugin 相关\n */\nconst pluginRE = /^nodeppt-plugin-/;\nconst scopeRE = /^@[\\w-]+\\//;\n\nexports.isPlugin = id => pluginRE.test(id);\nexports.resolvePluginId = id => {\n    // already full id\n    // e.g. vue-cli-plugin-foo, @vue/cli-plugin-foo, @bar/vue-cli-plugin-foo\n    if (pluginRE.test(id)) {\n        return id;\n    }\n    // scoped short\n    // e.g. @vue/foo, @bar/foo\n    if (id.charAt(0) === '@') {\n        const scopeMatch = id.match(scopeRE);\n        if (scopeMatch) {\n            const scope = scopeMatch[0];\n            const shortId = id.replace(scopeRE, '');\n            return `${scope}nodeppt-plugin-${shortId}`;\n        }\n    }\n    // default short\n    // e.g. foo\n    return `nodeppt-plugin-${id}`;\n};\n"
  },
  {
    "path": "packages/nodeppt-shared-utils/lib/prepare-urls.js",
    "content": "const chalk = require('chalk');\nconst url = require('url');\nconst address = require('address');\n\nexports.prepareUrls = (protocol, host, port, pathname = '/') => {\n    const formatUrl = hostname =>\n        url.format({\n            protocol,\n            hostname,\n            port,\n            pathname\n        });\n    const prettyPrintUrl = hostname =>\n        url.format({\n            protocol,\n            hostname,\n            port: chalk.bold(port),\n            pathname\n        });\n\n    const isUnspecifiedHost = host === '0.0.0.0' || host === '::';\n    let prettyHost;\n    let lanUrlForConfig;\n    let lanUrlForTerminal = chalk.gray('unavailable');\n    if (isUnspecifiedHost) {\n        prettyHost = 'localhost';\n        try {\n            // This can only return an IPv4 address\n            lanUrlForConfig = address.ip();\n            if (lanUrlForConfig) {\n                // Check if the address is a private ip\n                // https://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces\n                if (/^10[.]|^172[.](1[6-9]|2[0-9]|3[0-1])[.]|^192[.]168[.]/.test(lanUrlForConfig)) {\n                    // Address is private, format it for later use\n                    lanUrlForTerminal = prettyPrintUrl(lanUrlForConfig);\n                } else {\n                    // Address is not private, so we will discard it\n                    lanUrlForConfig = undefined;\n                }\n            }\n        } catch (_e) {\n            // ignored\n        }\n    } else {\n        prettyHost = host;\n    }\n    const localUrlForTerminal = prettyPrintUrl(prettyHost);\n    const localUrlForBrowser = formatUrl(prettyHost);\n    return {\n        lanUrlForConfig,\n        lanUrlForTerminal,\n        localUrlForTerminal,\n        localUrlForBrowser\n    };\n};\n"
  },
  {
    "path": "packages/nodeppt-shared-utils/lib/spinner.js",
    "content": "/**\n * @file loading 转圈效果\n */\nconst ora = require('ora');\nconst chalk = require('chalk');\n\nconst spinner = ora();\nlet lastMsg = null;\n\nfunction getDefaultSymbol() {\n    return process.platform === 'win32' ? '√' : '✔';\n}\nexports.logWithSpinner = (symbol, msg) => {\n    if (!msg) {\n        msg = symbol;\n        symbol = chalk.green(getDefaultSymbol());\n    }\n\n    if (lastMsg) {\n        spinner.stopAndPersist({\n            symbol: lastMsg.symbol,\n            text: lastMsg.text\n        });\n    }\n\n    spinner.text = ' ' + msg;\n    lastMsg = {\n        symbol: symbol + ' ',\n        text: msg\n    };\n    spinner.start();\n};\nexports.updateSpinner = (symbol, msg) => {\n    if (!msg) {\n        msg = symbol;\n        symbol = chalk.green(getDefaultSymbol());\n    }\n\n    spinner.text = ' ' + msg;\n    lastMsg = {\n        symbol: symbol + ' ',\n        text: msg\n    };\n};\n\nexports.stopSpinner = persist => {\n    if (lastMsg && persist !== false) {\n        spinner.stopAndPersist({\n            symbol: lastMsg.symbol,\n            text: lastMsg.text\n        });\n    } else {\n        spinner.stop();\n    }\n    lastMsg = null;\n};\n\nexports.pauseSpinner = () => {\n    spinner.stop();\n};\n\nexports.resumeSpinner = () => {\n    spinner.start();\n};\nexports.failSpinner = msg => {\n    spinner.fail(msg);\n};\nexports.successSpinner = msg => {\n    spinner.succeed(msg);\n};\n"
  },
  {
    "path": "packages/nodeppt-shared-utils/lib/webpack-error.js",
    "content": "const chalk = require('chalk');\nconst rules = [\n    {\n        type: 'cant-resolve-loader',\n        re: /Can't resolve '(.*loader)'/,\n        msg: (e, match) => `Failed to resolve loader: ${chalk.yellow(match[1])}\\n` + `You may need to install it.`\n    }\n];\nexports.transformer = error => {\n    if (error.webpackError) {\n        const message = typeof error.webpackError === 'string' ? error.webpackError : error.webpackError.message || '';\n        for (const {re, msg, type} of rules) {\n            const match = message.match(re);\n            if (match) {\n                return Object.assign({}, error, {\n                    // type is necessary to avoid being printed as defualt error\n                    // by friendly-error-webpack-plugin\n                    type,\n                    shortMessage: msg(error, match)\n                });\n            }\n        }\n        // no match, unknown webpack error without a message.\n        // friendly-error-webpack-plugin fails to handle this.\n        if (!error.message) {\n            return Object.assign({}, error, {\n                type: 'unknown-webpack-error',\n                shortMessage: message\n            });\n        }\n    }\n    return error;\n};\n\nexports.formatter = errors => {\n    errors = errors.filter(e => e.shortMessage);\n    if (errors.length) {\n        return errors.map(e => e.shortMessage);\n    }\n};\n"
  },
  {
    "path": "packages/nodeppt-shared-utils/package.json",
    "content": "{\n    \"name\": \"nodeppt-shared-utils\",\n    \"version\": \"2.2.1\",\n    \"description\": \"\",\n    \"main\": \"index.js\",\n    \"scripts\": {\n        \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n    },\n    \"keywords\": [\n        \"presentation\",\n        \"powerpoint\",\n        \"slideshow\",\n        \"keynote\",\n        \"ppt\",\n        \"slide\",\n        \"revealjs\",\n        \"impressjs\",\n        \"markdown-it\",\n        \"posthtml\",\n        \"webpack\",\n        \"nodeppt\",\n        \"markdown\"\n    ],\n    \"author\": {\n        \"name\": \"Theo Wang\",\n        \"email\": \"ksky521@gmail.com\"\n    },\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"git://github.com/ksky521/nodeppt\"\n    },\n    \"license\": \"MIT\",\n    \"dependencies\": {\n        \"address\": \"^1.0.3\",\n        \"boxen\": \"^4.2.0\",\n        \"chalk\": \"^3.0.0\",\n        \"debug\": \"^4.1.1\",\n        \"fs-extra\": \"^8.1.0\",\n        \"git-clone\": \"^0.1.0\",\n        \"ora\": \"^4.0.3\",\n        \"readline\": \"^1.3.0\",\n        \"request\": \"^2.88.0\",\n        \"string.prototype.padstart\": \"^3.0.0\"\n    },\n    \"gitHead\": \"9fba34ba1a8cc3ab149c2447c313e25b1e25093e\"\n}\n"
  },
  {
    "path": "site/animation.md",
    "content": "\ntitle: nodeppt 动效演示\nspeaker: 三水清\nurl: https://github.com/ksky521/nodeppt\n\n<slide class=\"bg-gradient-v\" image=\"https://source.unsplash.com/nxfuA21kNHY/1440x1440 .dark\" :class=\"size-60\">\n\nAnimation{.text-subtitle.animated.fadeInDown.delay-800}\n# nodeppt {.text-landing.animated.fadeInRight}\n\nThis is probably the best **web presentation tool** so far! {.text-intro.animated.fadeInUp.delay-800}\n\n[:fa-github: Github](https://github.com/ksky521/nodeppt){.button.ghost.animated.flipInX.delay-1500}\n\n<slide :class=\"aligncenter fadeInUp animated\">\n\n## 使用 `.animated` 给元素添加动效\n\n`.animated.fadeInUp` <br/>`.animated` 添加的动效是自动播放的\n\n<slide :class=\"aligncenter animated zoomIn size-40\">\n\n![](https://webslides.tv/static/images/android.png)\n\n<slide :class=\"aligncenter\">\n\n## `h2.animated.fadeIn.slow` {.fadeIn.animated.slow}\n\n<slide :class=\"aligncenter\">\n\n## `.animated.fadeIn.delay-800` {.fadeIn.animated.delay-800}\n\n<slide class=\"aligncenter\">\n\nanimate with {.text-subtitle}\n## `.tobuild` {.tada.animated.delay-500}\n\n\n需要添加动效的元素添加 `.tobuild` + 动效class {.tobuild.fadeInRight}\n\n`.tobuild` 动效是手动触发的 {.tobuild.fadeInLeft}\n\n<slide :class=\"size-50\">\n\n### `.build` 子元素全部会被添加`.tobuild`\n\n- `.build` + `.moveIn`\n- `.build` + `.moveIn`\n- `.build` + `.moveIn`\n- `.build` + `.moveIn`\n- `.build` + `.moveIn`\n{.build.moveIn}\n\n\n<slide :class=\"size-50\">\n### **animate.css** + `.build`\n\n---\n\n1. **fadeIn**{.bounce}\n2. **swing**{.swing}\n3. **flash**{.flash}\n4. **pulse**{.pulse}\n5. **shake**{.shake}\n6. **bounceIn**{.bounceIn}\n7. **wobble**{.wobble}\n8. **fadeInLeft**{.fadeInLeft}\n9. **flipInX**{.flipInX}\n10. **tada**{.tada}\n11. **slideInUp**{.slideInUp}\n12. **jello**{.jello}\n13. **heartBeat**{.heartBeat}\n14. **fadeInUp**{.fadeInUp}\n15. **lightSpeedIn**{.lightSpeedIn}\n{.text-cols.build}\n\n<slide>\n\n## nodeppt 使用 Prismjs 做语法高亮\n\n:::column {.vertical-align}\n\n* `.fadeInUp` + `.slow`\n* Highlights embedded languages (e.g. CSS inside HTML, JavaScript inside HTML)\n* Highlights inline code as well, not just code blocks\n* Highlights nested languages (CSS in HTML, JavaScript in HTML)\n\n---\n```html {..fadeInUp..slow}\n<article id=\"webslides\">\n  <!-- Slide 1 -->\n  <section>\n    <h1>Design for trust</h1>\n  </section>\n  <!-- Slide 2 -->\n  <section class=\"bg-primary\">\n    <div class=\"wrap\">\n      <h2>.wrap = container (width: 90%)</h2>\n    </div>\n  </section>\n</article>\n```\n:::\n\n<slide class=\"frame\" :class=\"size-60 bg-white tobuild bounce\">\n\n### :fa-info-circle large: **Autoplay Feature**\n\nAutoplay is generally disabled on all mobile devices to prevent bandwidth consumption. User must execute the play manually. {.text-intro}\n\n\n<slide class=\"bg-black aligncenter\" image=\"https://source.unsplash.com/n9WPPWiPPJw/ .anim\">\n\n## .background.anim\n\n\n<slide class=\"bg-primary\">\n# Design :for: understanding\n:::flexblock {.features.fadeInUp}\n\n:100^%^: purpose\n## Businesses that people love\n\n---\n## :fa-heart-o: Principles\n\nUseful → Easy → Fast → Beautiful\n:::\n\n<slide image=\"https://source.unsplash.com/yssUhIxbUZA/\">\n\n::: div {.content-left.bg-trans-dark.animated.fadeInRight}\n!![](https://webslides.tv/static/images/logos/airbnb.svg .whitelogo)\n\n---\n\n## **Designing Experiences**\n\n自动播放的**animate.css**\n:::\n\n\n<slide class=\"bg-primary\" :class=\"size-60 frame\">\n\n## View More Demos? {.text-serif.aligncenter}\n\n\\* \\* \\* {.text-symbols}\n\n<nav class=\"aligncenter\">\n* [:fa-th-large: Layout](./layout.html)\n* [:fa-magic: Animation](./animation.html)\n* [:fa-cube: Component](./component.html)\n* [:fa-youtube: Media](./media.html)\n* [:fa-css3: Classes](./index.html)\n</nav>\n\n<slide class=\"aligncenter\">\n\n## U work so hard, **but** 干不过 write PPTs {.animated.tada}\n\n快使用 [nodeppt](https://github.com/ksky521/nodeppt) 轻松搞定高大上PPT<br/> nodeppt 助力你的人生逆袭之路！ {.text-into.animated.delay-800.fadeIn}\n\n[:fa-cloud-download: Github](https://github.com/ksky521/nodeppt){.button.animated.delay-1s.fadeInUp}\n"
  },
  {
    "path": "site/background.md",
    "content": "title: nodeppt 背景效果演示\nspeaker: 三水清\nurl: https://github.com/ksky521/nodeppt\njs:\n    - background.js\n\n<slide class=\"bg-black-blue aligncenter\" image=\"https://source.unsplash.com/6njoEbtarec/ .dark\">\n\nBackground{.text-subtitle.animated.fadeInDown.delay-800}\n# nodeppt {.text-landing.text-shadow}\n\n这可能是迄今为止最好的网页版演示库 {.text-intro}\n\n[:fa-github: Github](https://github.com/ksky521/nodeppt){.button.ghost}\n\n\n<slide class=\"bg-apple aligncenter\">\n\n# Backgrounds\n\n&lt;slide class=\"bg-apple\"&gt;\n\n<slide :class=\"size-40\">\n\n## 点击切换背景class\n\n* [`.bg-primary`](){onclick=\"changeBackgroundColor('bg-primary')\"}\n* [`.bg-secondary`](){onclick=\"changeBackgroundColor('bg-secondary')\"}\n* [`.bg-light`](){onclick=\"changeBackgroundColor('bg-light')\"}\n* [`.bg-black`](){onclick=\"changeBackgroundColor('bg-black')\"}\n* [`.bg-black-blue`](){onclick=\"changeBackgroundColor('bg-black-blue')\"}\n* [`.bg-red`](){onclick=\"changeBackgroundColor('bg-red')\"}\n* [`.bg-blue`](){onclick=\"changeBackgroundColor('bg-blue')\"}\n* [`.bg-green`](){onclick=\"changeBackgroundColor('bg-green')\"}\n* [`.bg-purple`](){onclick=\"changeBackgroundColor('bg-purple')\"}\n* [`.bg-trans-light`](){onclick=\"changeBackgroundColor('bg-trans-light')\"}\n* [`.bg-trans-dark`](){onclick=\"changeBackgroundColor('bg-trans-dark')\"}\n* [`.bg-apple`](){onclick=\"changeBackgroundColor('bg-apple')\"}\n* [`.bg-gradient-h`](){onclick=\"changeBackgroundColor('bg-gradient-h')\"}\n* [`.bg-gradient-r`](){onclick=\"changeBackgroundColor('bg-gradient-r')\"}\n* [`.bg-gradient-v`](){onclick=\"changeBackgroundColor('bg-gradient-v')\"}\n{.text-cols}\n\n<slide>\n## Corporate Backgrounds\n:::flexblock {.blink.border}\n\n## .bg-primary {..bg-primary}\n\n\\#44d\n\n----\n\n## .bg-secondary {..bg-secondary}\n\n\\#67d\n\n----\n\n## .bg-light {..bg-light}\n\n\\#edf2f7\n\n----\n\n## body\n\n\\#f7f9fb\n\n:::\n\n----\n\n## General Colors\n:::flexblock {.blink.border}\n\n## .bg-black {..bg-black}\n\n\\#111\n\n----\n\n## .bg-black-blue {..bg-black-blue}\n\n\\#123\n\n----\n\n## .bg-white {..bg-white}\n\n\\#fff\n:::\n\n<slide>\n\n## Colorful\n:::flexblock {.border.blink}\n\n## .bg-red {..bg-red}\n\n\\#c23\n\n----\n\n## .bg-green {..bg-green}\n\n\\#077\n\n----\n\n## .bg-blue {..bg-blue}\n\n\\#346\n\n----\n\n## .bg-purple {..bg-purple}\n\n\\#62b\n\n:::\n\n----\n\n### Transparent Backgrounds\n\n:::flexblock {.border.blink}\n\n## .bg-trans-dark {..bg-trans-dark}\n\nrgba(0, 0, 0, 0.5)\n\n----\n\n## .bg-trans-light {..bg-trans-light}\n\nrgba(255, 255, 255, 0.2)\n\n:::\n\n<slide class=\"bg-gradient-h\">\n# Gradients\n\n:::flexblock {.border}\n\nHorizontal\n`.bg-gradient-h`\n\n----\n\n Radial\n`.bg-gradient-r`\n\n----\nVertical\n`.bg-gradient-v`\n:::\n\n<slide class=\"bg-gradient-v aligncenter\">\n## Vertical Gradient\n\n`.bg-gradient-v`\n\n<slide class=\"bg-gradient-r aligncenter\">\n## Radial Gradient\n\n`.bg-gradient-r`\n\n<slide class=\"bg-black\" video=\"https://webslides.tv/static/videos/working.mp4 poster='https://webslides.tv/static/images/working.jpg'\" >\n\n\n`.background-video`\n\n## **WebSlides is the easiest way to make HTML presentations. Inspire and engage.**\n\n<slide class=\"bg-blue aligncenter\" video=\"https://webslides.tv/static/videos/working.mp4 poster='https://webslides.tv/static/images/working.jpg' .dark\">\n\n\n## BG Video with Overlay {.text-landing}\n\n`<slide class=\"bg-blue aligncenter\" video=\"https://webslides.tv/static/videos/working.mp4 poster='https://webslides.tv/static/images/working.jpg' .dark\">` or `.light`\n\n\n<slide class=\"fullscreen bg-blue\" youtube=\".dark id='_m67JbGjWnc' autoplay loop\" :class=\"aligncenter\">\n\n## **Youtube Background**\n\n`<slide youtube=\".dark id='_m67JbGjWnc' autoplay loop\">`\n\n<slide image=\"https://webslides.tv/static/images/iphone-hand.png .right-bottom\">\n\n:::{.content-left}\n### .background-(position)\n\n:::flexblock {.specs}\n::fa-wifi::\n\n## Ultra-Fast WiFi\nSimple and secure file sharing.\n\n---\n::fa-battery-full::\n\n## All day battery life\nYour battery worries may be over.\n\n---\n::fa-life-ring::\n## All day battery life\nWe'll fix it or if we can't, we'll replace it.\n\n:::\n\n<slide class=\"bg-black aligncenter\" image=\"https://source.unsplash.com/UJbHNoVPZW0/ .dark\">\n\n# Iceland{.text-landing.text-shadow}\n\n`slide[class*=\"bg-\"] > .background.dark`\n\n<slide class=\"bg-black aligncenter\" image=\"https://source.unsplash.com/UJbHNoVPZW0/ .light\">\n\n# Iceland{.text-landing.text-shadow}\n\n`slide[class*=\"bg-\"] > .background.light`\n\n<slide class=\"bg-black aligncenter\" image=\"https://source.unsplash.com/n9WPPWiPPJw/ .anim\">\n\n## .background.anim\n\n<slide class=\"bg-primary\" :class=\"size-60 frame\">\n\n## View More Demos? {.text-serif.aligncenter}\n\n\\* \\* \\* {.text-symbols}\n\n<nav class=\"aligncenter\">\n* [:fa-th-large: Layout](./layout.html)\n* [:fa-magic: Animation](./animation.html)\n* [:fa-cube: Component](./component.html)\n* [:fa-youtube: Media](./media.html)\n* [:fa-css3: Classes](./index.html)\n</nav>\n\n<slide class=\"aligncenter\">\n\n## U work so hard, **but** 干不过 write PPTs {.animated.tada}\n\n快使用 [nodeppt](https://github.com/ksky521/nodeppt) 轻松搞定高大上PPT<br/> nodeppt 助力你的人生逆袭之路！ {.text-into.animated.delay-800.fadeIn}\n\n[:fa-cloud-download: Github](https://github.com/ksky521/nodeppt){.button.animated.delay-1s.fadeInUp}\n"
  },
  {
    "path": "site/classes.md",
    "content": "\ntitle: nodeppt 样式演示\nspeaker: 三水清\nurl: https://github.com/ksky521/nodeppt\nwebslidesOptions:\n    autoslide: 5000\n\n<slide class=\"bg-black-blue aligncenter\" image=\"https://source.unsplash.com/C1HhAQrbykQ/ .dark\">\n\nClasses{.text-subtitle.animated.fadeInDown.delay-800}\n# nodeppt {.text-landing.text-shadow}\n\n这可能是迄今为止最好的网页版演示库 {.text-intro}\n\n[:fa-github: Github](https://github.com/ksky521/nodeppt){.button.ghost}\n\n\n<slide :class=\"size-50\">\n\n##  :fa-heart-o: CSS Syntax\n\nWebSlides is so easy to understand and love. Baseline\\: 8. {.text-intro}\n\n* :Typography\\::{.text-label}  .text-landing, .text-subtitle, .text-data, .text-intro...\n* :BG Colors\\::{.text-label}  .bg-primary, .bg-blue,.bg-apple...\n* :BG Images\\::{.text-label} .background, .background-center-bottom...\n* :Cards\\::{.text-label} .card-60, .card-50, .card-40...\n* :Sizes\\::{.text-label} .size-50, .size-40...\n* :Flex Blocks\\::{.text-label} .flexblock.clients, .flexblock.gallery, .flexblock.metrics...\n{.description}\n\n\n<slide>\n\nOptional · 500+ icons {.text-subtitle}\n## [:fa-flag: Font Awesome](http://fontawesome.io/icons/) as SVG icons\n\n```markdown\n:fa-flag:\n```\n\n\n<slide>\n\n:::column {.vertical-align}\n### **WebSlides is really easy**\nEach parent `<section>` in the #webslides element is an individual slide. {.text-intro}\n\nCode is neat, scalable, and well documented. It uses **intuitive markup with popular naming conventions**. There's no need to overuse classes or nesting. **Based on** [SimpleSlides](https://github.com/jennschiffer/SimpleSlides) , by [Jenn Schiffer](http://jennmoney.biz) :)\n\n----\n```html\n<article id=\"webslides\">\n  <!-- Slide 1 -->\n  <section>\n    <h1>Design for trust</h1>\n  </section>\n  <!-- Slide 2 -->\n  <section class=\"bg-primary\">\n    <div class=\"wrap\">\n      <h2>.wrap = container (width: 90%) with fadein</h2>\n    </div>\n  </section>\n</article>\n```\n:::\n\n---\nVertical sliding? `<article id=\"webslides\" class=\"vertical\">` {.aligncenter}\n\n\n<slide>\n\n:::{.aligncenter}\n### Simple CSS Alignments\n\nPut content wherever you want.\n:::\n\n:::footer\nFooter: logo, credits... (.alignleft) {.alignleft}\n\n[:fa-twitter: @username .alignright](){.alignright}\n\n:::\n\n:::header\nHeader (logo) :.alignright:{.alignright}\n:::\n\n\n<slide>\n\n!![](https://webslides.tv/static/images/iphone.png .size-50.alignleft)\n\n## img.alignleft\n`img.alignleft.size-50`\n\nJobs unveiled the iPhone to the public on January 9, 2007, at the Macworld 2007 convention at the Moscone Center in San Francisco.  Apple sold 6.1 million first generation iPhone units over five quarters.\n\n**Image size recommended**:<br> 800x600px / 600x450px.\n\n\n<slide>\n\n!![](https://webslides.tv/static/images/iphone.png .size-50.alignright)\n\n## img.alignright\n`img.alignright.size-50`\n\nJobs unveiled the iPhone to the public on January 9, 2007, at the Macworld 2007 convention at the Moscone Center in San Francisco.  Apple sold 6.1 million first generation iPhone units over five quarters.\n\n**Image size recommended**:<br> 800x600px / 600x450px.\n\n<slide>\n\n!![](https://webslides.tv/static/images/iphone.png .size-40.aligncenter)\n\n`img.aligncenter.size-40` {.aligncenter}\n\n\n\n\n<slide class=\"aligncenter\">\n# Landings {.text-landing}\n\n`.text-landing`\n\n<slide class=\"aligncenter\">\n# Landings {.text-landing}\n\nCreate a simple web presence. {.text-intro}\n\n`.text-intro`\n\n<slide class=\"aligncenter\">\n\nPOWERED BY [#WEBSLIDES](https://twitter.com/search?f=tweets&vertical=default&q=%23WebSlides&src=typd) `.text-subtitle` {.text-subtitle}\n\n# Landings {.text-landing}\n\nCreate a simple web presence. {.text-intro}\n\n`.text-intro`\n\n<slide class=\"bg-black aligncenter\" image=\"https://source.unsplash.com/C1HhAQrbykQ/\">\n# **Landings** {.text-landing.text-shadow}\n\n`.text-shadow` {.text-intro}\n\n<slide class=\"bg-apple aligncenter\">\n## 4,235,678 {.text-data}\n\n`.text-data`\n\n<slide>\n\nWhy WebSlides? .text-context {.text-content}\n\n## WebSlides is incredibly easy and versatile. The easiest way to make HTML presentations.\n\n<slide>\n\n`.text-cols (2 columns)`\n\n:::div {.text-cols}\n\n**Why WebSlides?** There are excellent presentation tools out there. WebSlides is about sharing content, essential features, and clean markup. **Each parent &lt;slide&gt;**  in the #webslides element is an individual slide.\n\n**WebSlides help you build a culture of innovation and excellence**. When you're really passionate about your job, you can change the world. How to manage a design-driven organization? Leadership through usefulness, openness, empathy, and good taste.\n\n:::\n\n:::flexblock {.metrics}\n\n:fa-phone:\n\nCall us at 555.345.6789\n\n----\n\n:fa-twitter:\n\n@username\n\n----\n:fa-envelope:\nSend us an email\n:::\n\n<slide>\n:::column {.vertical-align}\n\n## A Phone by Google\nPixel's camera lets you take brilliant photos in low light, bright light or any light. {.text-intro}\n\n* :Typography\\::{.text-label}  .text-landing, .text-subtitle, .text-data, .text-intro...\n* :BG Colors\\::{.text-label}  .bg-primary, .bg-blue,.bg-apple...\n* :BG Images\\::{.text-label} .background, .background-center-bottom...\n* :Sizes\\::{.text-label} .size-50, .size-40...\n* :Flex Blocks\\::{.text-label} .flexblock.clients, .flexblock.gallery, .flexblock.metrics...\n{.description}\n\n----\n![](https://webslides.tv/static/images/android.png)\n\n:::\n\n<slide class=\"aligncenter text-serif\">\n\n:::div {.content-left}\n## WebSlides is incredibly easy and versatile.\n`.text-serif`  (Maitree)\n:::\n\n:::div {.content-left}\n\nEach parent `<slide>` in the #webslides element is an individual slide.\n\nClean markup with popular naming conventions. Minimum effort. Just focus on your content.\n\n:::\n\n<slide :class=\"size-50\">\n\n### **What is Stendhal Syndrome?**\n\nBeauty overdose. `.text-pull-right` {.text-intro}\n\nImagine that you are in Florence. If you suddenly start to feel that you literally cannot breathe, you may be experiencing Stendhal Syndrome.\n\nPsychiatrists have long debated whether it really exists. {.text-pull-right}\n\nThe syndrome is not only associated with viewing a beautiful place, but also good art.\n\nThe beauty of Italian art has a concentrated perfection and transcendent sensuality that is incredibly addictive.\n\n<slide class=\"bg-primary\">\n# Design :for: understanding\n:::flexblock {.features.fadeInUp}\n\n:100^%^: purpose\n## Businesses that people love\n\n---\n## :fa-heart-o: Principles\n\nUseful → Easy → Fast → Beautiful\n:::\n\n<slide image=\"https://source.unsplash.com/yssUhIxbUZA/\">\n\n::: div {.content-left.bg-trans-dark.fadeInUp}\n!![](https://webslides.tv/static/images/logos/airbnb.svg .whitelogo)\n\n---\n\n## **Designing Experiences**\n\nMeet locals who share your interests.\n:::\n\n<slide class=\"bg-black slide-bottom\" image=\"https://source.unsplash.com/RSOxw9X-suY/\">\n\n:::div {.content-left}\n:fa-tree large:\n\n## 1,000,000\n### We're working to protect up to a million acres of sustainable forest.\n\n:::\n\n<slide class=\"bg-black-blue\">\n\n:::column\n### **:fa-line-chart: Interface**\n\nDesign for growth. We've built a team of world-class designers, developers, and managers.\n\n---\n### **:fa-film: Videos**\n\nWe connect your audience needs, business goals, and brand values into a strategy.\n\n---\n### **:fa-users: Recruiting**\n\nWe offer personalized services with deep expertise in design and technology.\n\n---\n### **:fa-graduation-cap: Formation**\n\nWe train teams to help organizations succeed in the digital age.\n:::\n\n\n<slide>\n## table\n\n| Left-aligned | Center-aligned | Right-aligned |\n| :----------- | :------------: | ------------: |\n| git status   |   git status   |    git status |\n| git diff     |    git diff    |      git diff |\n| git status   |   git status   |    git status |\n\n<slide class=\"aligncenter\">\n\n## avatar\n\n![](https://avatars2.githubusercontent.com/u/1073262?s=40&v=4){.avatar-40}\n\n (80, 72, 64, 56, 48, and 40).\n\n<slide class=\"aligncenter\">\n\n## Quote\n\n<slide class=\"bg-black-blue\" :class=\"size-50\">\n> I have always appreciated designers who dare to reinterpret fabrics and proportions, so I follow the Japanese and Belgian designers.\n> ==Zaha Hadid==\n> {.text-quote}\n\n<slide image=\"https://webslides.tv/static/images/satya.png .left-bottom\">\n\n:::div {.content-right}\n> \"There is something only a CEO uniquely can do, which is set that tone, which can then capture the soul of the collective.\"\n> ==Satya Nadella, CEO of Microsoft.==\n:::\n\n\n<slide>\n:::card {.quote}\n\n\n\n![](https://webslides.tv/static/images/davinci.png)\n\n---\n> “WebSlides helped us build a culture of innovation and excellence.”\n> ==Leonardo da Vinci==\n\n\n\n<slide>\n\n::: {.content-left}\n## button\n\n[.button](){.button} [.button.radius](){.button.radius}\n\n[.button.ghost](){.button.ghost} [:fa-github: svg-icon](){.button}\n:::\n\n\n<slide class=\"bg-primary\" :class=\"size-50 frame\">\n\n## View More Demos? {.text-serif.aligncenter}\n\n\\* \\* \\* {.text-symbols}\n\n<nav class=\"aligncenter\">\n* [:fa-th-large: Layout](./layout.html)\n* [:fa-tv: Background](./background.html)\n* [:fa-magic: Animation](./animation.html)\n* [:fa-cube: Component](./component.html)\n* [:fa-youtube: Media](./media.html)\n* [:fa-css3: Classes](./classes.html)\n</nav>\n\n<slide class=\"aligncenter\">\n\n## U work so hard, **but** 干不过 write PPTs\n\n快使用 [nodeppt](https://github.com/ksky521/nodeppt) 轻松搞定高大上PPT<br/> nodeppt 助力你的人生逆袭之路！ {.text-into}\n\n[:fa-cloud-download: Github](https://github.com/ksky521/nodeppt){.button}\n"
  },
  {
    "path": "site/component.md",
    "content": "title: nodeppt 组件演示\nspeaker: 三水清\nurl: https://github.com/ksky521/nodeppt\n\n<slide class=\"bg-black-blue aligncenter\" image=\"https://source.unsplash.com/C1HhAQrbykQ/ .dark\">\n\nComponent{.text-subtitle.animated.fadeInDown.delay-800}\n# nodeppt {.text-landing.text-shadow}\n\n这可能是迄今为止最好的网页版演示库 {.text-intro}\n\n[:fa-github: Github](https://github.com/ksky521/nodeppt){.button.ghost}\n\n\n<slide class=\"aligncenter\">\n\n## **Grid column**\n\n`:::comlumn`\n\n\n<slide class=\"aligncenter\">\n## .grid + .column\nBasic Grid (auto-fill and equal height). {.text-intro}\n\n:::column\n\n###### Why WebSlides?\n\nThere're excellent presentation tools out there. WebSlides is about good karma and sharing content. Hypertext, clean code, and beauty as narrative elements.\n\n\n----\n\n!![figure](https://webslides.tv/static/images/setup.png .aligncenter)\n\n---\n###### How easy is WebSlides?\nYou can create your own presentation instantly. Just a basic knowledge of HTML and CSS is required. Simply choose a demo and customize it.\n\n:::\n\n<slide class=\"aligncenter\">\n## .grid.**vertical-align** + .column\nBasic Grid (auto-fill and equal height). {.text-intro}\n\n:::column {.vertical-align}\n\n###### Why WebSlides?\n\nThere're excellent presentation tools out there. WebSlides is about good karma and sharing content. Hypertext, clean code, and beauty as narrative elements.\n\n\n----\n\n!![figure](https://webslides.tv/static/images/setup.png .aligncenter)\n\n---\n###### How easy is WebSlides?\nYou can create your own presentation instantly. Just a basic knowledge of HTML and CSS is required. Simply choose a demo and customize it.\n\n:::\n\n\n<slide>\n## .grid.**sm**  (sidebar + main)\n----\n\n:::column {.sm}\n\n### .column 1\n\nStendhal syndrome is a psychosomatic disorder that causes rapid heartbeat, dizziness, fainting, confusion and even hallucinations when an individual is exposed to an experience of great personal significance, particularly viewing art.\n\n---\n\n## .column 2\nThe illness is named after the 19th-century French author Stendhal (pseudonym of Marie-Henri Beyle), who described his experience with the phenomenon during his 1817 visit to Florence in his book Naples and Florence: A Journey from Milan to Reggio.\n\nWhen he visited the Basilica of Santa Croce, where Niccolò Machiavelli, Michelangelo and Galileo Galilei are buried, he saw Giotto's frescoes for the first time and was overcome with emotion.\n\n\n:::\n\n\n<slide>\n## .grid.**ms**  ( main + sidebar)\n----\n\n:::column {.ms}\n\n### .column 1\nThe illness is named after the 19th-century French author Stendhal (pseudonym of Marie-Henri Beyle), who described his experience with the phenomenon during his 1817 visit to Florence in his book Naples and Florence: A Journey from Milan to Reggio.\n\nWhen he visited the Basilica of Santa Croce, where Niccolò Machiavelli, Michelangelo and Galileo Galilei are buried, he saw Giotto's frescoes for the first time and was overcome with emotion.\n\n\n---\n\n## .column 2\n\nStendhal syndrome is a psychosomatic disorder that causes rapid heartbeat, dizziness, fainting, confusion and even hallucinations when an individual is exposed to an experience of great personal significance, particularly viewing art.\n\n:::\n\n\n<slide>\n## .grid.**sms**  ( sidebar + main + sidebar)\n----\n\n:::column {.sms}\n\n### .column 1\n\nInformation architecture is considered to have been founded by Richard Saul Wurman.\n\n----\n### .column 2\n\nInformation architecture (IA) is the structural design of shared information environments; the art and science of organizing and labelling websites, intranets, online communities and software to support usability and findability; and an emerging community of practice focused on bringing principles of design and architecture to the digital landscape.\n\n\n---\n\n## .column 3\nThe difficulty in establishing a common definition for \"information architecture\" arises partly from the term's existence in multiple fields.\n\n:::\n\n<slide class=\"aligncenter\">\n\n## **Card**\n\n`:::card` `:::card-50` `:::card-60`...\n\n<slide :class=\"size-80\">\n\n:::card\n\n\n## Unsplash\n.card-50.bg-white\n\n [Unsplash](http://Unsplash.com) is a really cool resource. It is a collection of Creative Commons Zero licensed photos that are really great. {.text-intro}\n\n* :Role\\::{.text-label} Frontend\n* :client\\::{.text-label} Acme\n* :year\\::{.text-label} 2018\n{.description}\n\n---\n![](https://source.unsplash.com/rCOWMC8qf8A/)\n\n:::\n\n\n<slide :class=\"size-80\">\n\n:::card-50\n\n![](https://source.unsplash.com/HoevDVvxInw/960x720)\n\n---\n\n## Bonsai\n\nBonsai is a Japanese art form using trees grown in containers — **.card-50.** {.text-intro}\n\nSimilar practices exist in other cultures, including the Chinese tradition of penjing from which the art originated, and the miniature living landscapes of Vietnamese hòn non bộ.\n\n\\* \\* \\* {.text-symbols}\n\nSimilar practices exist in other cultures, including the Chinese tradition of penjing from which the art originated, and the miniature living landscapes of Vietnamese hòn non\n:::\n\n<slide :class=\"size-80\">\n\n:::card-60\n\n![](https://source.unsplash.com/HoevDVvxInw/960x720)\n\n---\n\n## Bonsai\n\nBonsai is a Japanese art form using trees grown in containers —  **.card-60.** {.text-intro}\n\nSimilar practices exist in other cultures, including the Chinese tradition of penjing from which the art originated, and the miniature living landscapes of Vietnamese hòn non bộ.\n\n\\* \\* \\* {.text-symbols}\n\nSimilar practices exist in other cultures, including the Chinese tradition of penjing from which the art originated, and the miniature living landscapes of Vietnamese hòn non\n:::\n\n<slide :class=\"size-80\">\n\n:::card-30\n\n![](https://source.unsplash.com/HoevDVvxInw/960x720)\n\n---\n\n## Bonsai\n\nBonsai is a Japanese art form using trees grown in containers —  **.card-30.** {.text-intro}\n\nSimilar practices exist in other cultures, including the Chinese tradition of penjing from which the art originated, and the miniature living landscapes of Vietnamese hòn non bộ.\n\n\\* \\* \\* {.text-symbols}\n\nSimilar practices exist in other cultures, including the Chinese tradition of penjing from which the art originated, and the miniature living landscapes of Vietnamese hòn non\n:::\n\n<slide class=\"fullscreen\">\n\n:::card\n\n![](https://source.unsplash.com/ALtNa-uKy3M/)\n\n---\n\n## Bonsai\n\nBonsai is a Japanese art form using trees grown in containers — **.fullscreen > .card-50.** {.text-intro}\n\nSimilar practices exist in other cultures, including the Chinese tradition of penjing from which the art originated, and the miniature living landscapes of Vietnamese hòn non bộ.\n\n:::\n\n\n<slide :class=\"size-70\">\n## Quote Card {.aligncenter}\n\n:::card {.quote}\n\n\n![](https://webslides.tv/static/images/davinci.png)\n\n---\n> “WebSlides helped us build a culture of innovation and excellence.”\n> ==Leonardo da Vinci==\n\n\n\n<slide class=\"aligncenter\">\n\n## **Flexible blocks**\n\n`:::flexblock` = Flexible blocks with auto-fill and equal height.\n\n\n\n<slide>\n\n## flexblock\n\n:::flexblock\n## :fa-bar-chart: Purpose\nBusinesses that people love5\n\n---\n\n## :fa-bar-chart: Purpose\nBusinesses that people love6\n\n---\n\n## :fa-balance-scale: Purpose\nBusinesses that people love7\n\n---\n\n\n## :fa-cog: Purpose\nBusinesses that people love8\n\n:::\n\n----\n## flexblock\n\n`{.blink.border}`\n\n:::flexblock {.blink.border}\n## :fa-bar-chart: Purpose\nBusinesses that people love1\n\n---\n\n## :fa-bar-chart: Purpose\nBusinesses that people love2\n\n---\n\n## :fa-balance-scale: Purpose\nBusinesses that people love3\n\n---\n\n\n## :fa-cog: Purpose\nBusinesses that people love4\n\n:::\n\n\n<slide>\n\n## flexblock clients\n\n`{.clients}`\n\n:::flexblock {.clients}\n\n![](https://webslides.tv/static/images/logos/google.svg){.blacklogo}\n### Interfaces\nCollaboration with the Acme team to design their mobile apps.\n\n---\n![](https://webslides.tv/static/images/logos/microsoft.svg) {.blacklogo}\n\n### Interfaces\nCollaboration with the Acme team to design their mobile apps.\n\n---\n![](https://webslides.tv/static/images/logos/instagram.svg){.blacklogo}\n\n### Interfaces\nCollaboration with the Acme team to design their mobile apps.\n\n---\n![](https://webslides.tv/static/images/logos/netflix.svg){.blacklogo}\n\n\n### Interfaces\nCollaboration with the Acme team to design their mobile apps.\n\n:::\n\n<slide>\n## flexblock clients\n\n`{.clients.border}`\n\n:::flexblock {.clients.border}\n\n![](https://webslides.tv/static/images/logos/google.svg){.blacklogo}\n### Interfaces\nCollaboration with the Acme team to design their mobile apps.\n\n---\n![](https://webslides.tv/static/images/logos/microsoft.svg) {.blacklogo}\n\n### Interfaces\nCollaboration with the Acme team to design their mobile apps.\n\n---\n![](https://webslides.tv/static/images/logos/instagram.svg){.blacklogo}\n\n### Interfaces\nCollaboration with the Acme team to design their mobile apps.\n\n---\n![](https://webslides.tv/static/images/logos/netflix.svg){.blacklogo}\n\n\n### Interfaces\nCollaboration with the Acme team to design their mobile apps.\n\n:::\n\n<slide>\n### ul.flexblock.features\n\n:::flexblock {.features}\n\n## :100 ^%^:  customizable\n\n Well documented\n\n\n ----\n\n:^$^48:\n## EXTRA VIRGIN OLIVE OIL\n\nThe Spanish caviar.\n\n----\n## :fa-wifi: Ultra-fast Wifi\n\nSimple file sharing.\n:::\n\n---\n\n### ul.flexblock.features.blink\n\n:::flexblock {.features.blink}\n\n## :100 ^%^:  customizable\n\n Well documented\n\n\n ----\n\n:^$^48:\n## EXTRA VIRGIN OLIVE OIL\n\nThe Spanish caviar.\n\n----\n## :fa-wifi: Ultra-fast Wifi\n\nSimple file sharing.\n:::\n\n\n<slide class=\"bg-green\">\n\n## flexblock .Metrics\n\n:::flexblock {.border.metrics}\n\nFounded\n::1972::\n\n----\n\n::fa-users::\n\n24M Subscribers\n\n\n---\n\nFounded\n::64%::\n\n----\n\n:~fa-line-chart~:\n\nRevenue: $16M\n\n---\n\n:~fa-building-o~:\n\nCovers, cards, quotes...\n\n----\n\n:~fa-smile-o~:\n\nUse multiples of 8.\n\n---\n\n:~fa-usd~:\n\nFont Awesome Kit.\n\n---\n\n:~fa-university~:\n\nBank: $32M\n\n:::\n\n<slide :class=\"size-60\">\n\n## shadowbox\n\n`:::shadowbox`\n\n---\n\n:::shadowbox\n\n## We're web people.\n\nThere're excellent presentation tools out there. WebSlides is about telling the story, and sharing it in a beautiful way. Hypertext and clean code as narrative elements.\n\n---\n\n## Work better, faster.\n\nDesigners, marketers, and journalists can now focus on the content. Simply [choose a demo](https://webslides.tv/demos) and customize it in minutes.\n\n:::\n\n<slide :class=\"size-80\">\n## steps\n`:::steps`\n\n---\n:::steps\n\n:~fa-file~:\n## Interfaces\n\nWhen you're really passionate about your job, you can change the world.\n\n---\n\n## Interfaces\n\n1. Architecture\n2. Design\n3. Development\n\n---\n\n## Interfaces\n\n1. Architecture\n2. Design\n3. Development\n\n---\n\n## Interfaces\n\n1. Architecture\n2. Design\n3. Development\n\n:::\n\n<slide :class=\"size-70\">\n### gallery\n\n`:::gallery`\n\n:::gallery\n\n![](https://source.unsplash.com/uPGOEbjbVGA/800x600)\n\n## Alicia Jiménez\n\nFounder & CEO\n\n---\n\n![](https://source.unsplash.com/6anudmpILw4/800x600)\n\n## Sam Trololovitz\n\nMaster of nothing\n\n---\n\n![](https://source.unsplash.com/IFxjDdqK_0U/800x600)\n\n## Erin Gustafson\n\nVP of Design\n\n:::\n\n<slide :class=\"size-80\">\n### gallery overlay\n`:::gallery-overlay`\n\n:::gallery-overlay\n\n![](https://source.unsplash.com/uPGOEbjbVGA/800x600)\n\n## Alicia Jiménez\n\nFounder & CEO\n\n---\n\n![](https://source.unsplash.com/zhkTCCmD4xI/800x600)\n\n## Sam Trololovitz\n\nCTO\n\n---\n\n![](https://source.unsplash.com/IFxjDdqK_0U/800x600)\n\n## Erin Gustafson\n\nVP of Design\n\n:::\n\n<slide class=\"aligncenter\">\n\n## **CTA**\n\n`:::cta` 不要问我为啥取这个名字。。\n\n<slide class=\"bg-red\" image=\"https://source.unsplash.com/R1J6Z1cnJZc/ .dark\">\n\n\n:::cta\n\n!![](https://webslides.tv/static/images/logos/netflix.svg .whitelogo)\n\n---\n\n## Watch TV shows anytime, anywhere\n\n.frame.bg-red\n\n:::\n\n<slide class=\"bg-red frame\">\n\n:::cta\n\n::^$^40::\n\n---\n\n## Watch TV shows anytime, anywhere\n\n.frame.bg-red\n\n:::\n\n\n<slide class=\"bg-primary\" :class=\"size-60 frame\">\n\n## View More Demos? {.text-serif.aligncenter}\n\n\\* \\* \\* {.text-symbols}\n\n<nav class=\"aligncenter\">\n* [:fa-th-large: Layout](./layout.html)\n* [:fa-tv: Background](./background.html)\n* [:fa-magic: Animation](./animation.html)\n* [:fa-youtube: Media](./media.html)\n* [:fa-css3: Classes](./index.html)\n</nav>\n\n<slide class=\"aligncenter\">\n\n## U work so hard, **but** 干不过 write PPTs\n\n快使用 [nodeppt](https://github.com/ksky521/nodeppt) 轻松搞定高大上PPT<br/> nodeppt 助力你的人生逆袭之路！ {.text-into}\n\n[:fa-cloud-download: Github](https://github.com/ksky521/nodeppt){.button}\n"
  },
  {
    "path": "site/echarts.md",
    "content": "title: nodeppt 富媒体&插件演示\nspeaker: 三水清\nurl: https://github.com/ksky521/nodeppt\njs:\n    - https://echarts.cdn.apache.org/zh/asset/theme/infographic.js\nplugins:\n    - echarts: {theme: infographic}\n\n<slide class=\"bg-black-blue aligncenter\" image=\"https://source.unsplash.com/Zq_K89I9E-8/ .dark\">\n\nEcharts {.text-subtitle.animated.fadeInDown.delay-800}\n# nodeppt {.text-landing.text-shadow}\n\n这可能是迄今为止最好的网页版演示库 {.text-intro}\n\n[:fa-github: Github](https://github.com/ksky521/nodeppt){.button.ghost}\n\n\n<slide class=\"aligncenter\">\n\n## Echarts\n\n<slide :class=\"size-60\">\n## Echarts {.aligncenter}\n\n\n<slide :class=\"size-60\">\n## echarts {.aligncenter}\n\n```echarts {style=\"height:100%;width:100%;\"}\n{\n    tooltip: {\n        trigger: 'item',\n        formatter: \"{a} <br/>{b}: {c} ({d}%)\"\n    },\n    legend: {\n        orient: 'vertical',\n        x: 'left',\n        data:['直达','营销广告','搜索引擎','邮件营销','联盟广告','视频广告','百度','谷歌','必应','其他']\n    },\n    series: [\n        {\n            name:'访问来源',\n            type:'pie',\n            selectedMode: 'single',\n            radius: [0, '30%'],\n\n            label: {\n                normal: {\n                    position: 'inner'\n                }\n            },\n            labelLine: {\n                normal: {\n                    show: false\n                }\n            },\n            data:[\n                {value:335, name:'直达', selected:true},\n                {value:679, name:'营销广告'},\n                {value:1548, name:'搜索引擎'}\n            ]\n        },\n        {\n            name:'访问来源',\n            type:'pie',\n            radius: ['40%', '55%'],\n            label: {\n                normal: {\n                    formatter: '{a|{a}}{abg|}\\n{hr|}\\n  {b|{b}：}{c}  {per|{d}%}  ',\n                    backgroundColor: '#eee',\n                    borderColor: '#aaa',\n                    borderWidth: 1,\n                    borderRadius: 4,\n                    // shadowBlur:3,\n                    // shadowOffsetX: 2,\n                    // shadowOffsetY: 2,\n                    // shadowColor: '#999',\n                    // padding: [0, 7],\n                    rich: {\n                        a: {\n                            color: '#999',\n                            lineHeight: 22,\n                            align: 'center'\n                        },\n                        // abg: {\n                        //     backgroundColor: '#333',\n                        //     width: '100%',\n                        //     align: 'right',\n                        //     height: 22,\n                        //     borderRadius: [4, 4, 0, 0]\n                        // },\n                        hr: {\n                            borderColor: '#aaa',\n                            width: '100%',\n                            borderWidth: 0.5,\n                            height: 0\n                        },\n                        b: {\n                            fontSize: 16,\n                            lineHeight: 33\n                        },\n                        per: {\n                            color: '#eee',\n                            backgroundColor: '#334455',\n                            padding: [2, 4],\n                            borderRadius: 2\n                        }\n                    }\n                }\n            },\n            data:[\n                {value:335, name:'直达'},\n                {value:310, name:'邮件营销'},\n                {value:234, name:'联盟广告'},\n                {value:135, name:'视频广告'},\n                {value:1048, name:'百度'},\n                {value:251, name:'谷歌'},\n                {value:147, name:'必应'},\n                {value:102, name:'其他'}\n            ]\n        }\n    ]\n}\n\n```\n\n\n<slide class=\"bg-primary\" :class=\"size-60 frame\">\n\n## View More Demos? {.text-serif.aligncenter}\n\n\\* \\* \\* {.text-symbols}\n\n<nav class=\"aligncenter\">\n* [:fa-th-large: Layout](./layout.html)\n* [:fa-tv: Background](./background.html)\n* [:fa-magic: Animation](./animation.html)\n* [:fa-cube: Component](./component.html)\n* [:fa-css3: Classes](./index.html)\n</nav>\n\n<slide class=\"aligncenter\">\n\n## U work so hard, **but** 干不过 write PPTs\n\n快使用 [nodeppt](https://github.com/ksky521/nodeppt) 轻松搞定高大上PPT<br/> nodeppt 助力你的人生逆袭之路！ {.text-into}\n\n[:fa-cloud-download: Github](https://github.com/ksky521/nodeppt){.button}\n"
  },
  {
    "path": "site/index.md",
    "content": "title: nodeppt - 这可能是迄今为止最好的网页版演示库\nspeaker: 三水清\nurl: https://github.com/ksky521/nodeppt\njs:\n    - https://echarts.cdn.apache.org/zh/asset/theme/infographic.js\nplugins:\n    - echarts: {theme: infographic}\n    - mermaid: {theme: forest}\n    - katex\n\n<slide class=\"bg-black-blue aligncenter\" image=\"https://cn.bing.com/az/hprichbg/rb/RainierDawn_EN-AU3730494945_1920x1080.jpg .dark\">\n\n# nodeppt {.text-landing.text-shadow}\n\n这可能是迄今为止最好的网页版演示库 {.text-intro.animated.fadeInUp.delay-500}\n\n[:fa-github: Github](https://github.com/ksky521/nodeppt){.button.ghost.animated.flipInX.delay-1200}\n\n<slide :class=\"size-30 aligncenter\">\n\n### Install\n\n---\n\n`npm install -g nodeppt` {.animated.fadeInUp}\n\n<slide :class=\"size-40 aligncenter\">\n\n### Commands\n\n---\n\n```shell {.animated.fadeInUp}\n # create a new slide with an official template\n$ nodeppt new slide.md\n\n# create a new slide straight from a github template\n$ nodeppt new slide.md -t username/repo\n\n# start local sever show slide\n$ nodeppt serve slide.md\n\n# to build a slide\n$ nodeppt build slide.md\n```\n\n<slide class=\"bg-gradient-r\" :class=\" size-40 aligncenter\" image=\"https://cn.bing.com/az/hprichbg/rb/WinterLynx_ZH-CN7158207296_1920x1080.jpg .dark\">\n\n## Demo Contents\n---\n\n* Keyboard Shortcuts {.animated.fadeInUp}\n* CSS Syntax {.animated.fadeInUp.delay-400}\n* Background {.animated.fadeInUp.delay-800}\n* Animation {.animated.fadeInUp.delay-1200}\n* Content Position {.animated.fadeInUp.delay-1600}\n* Quotes {.animated.fadeInUp.delay-2s}\n* Plugins\\: echarts/mermaid/ketax {.animated.fadeInUp.delay-2400}\n* Others\\: Button/Table.. {.animated.fadeInUp.delay-2800}\n* Speaker mode.. {.animated.fadeInUp.delay-3200}\n\n<slide :class=\"size-60 aligncenter\">\n\n## Keyboard Shortcuts\n\n---\n\n-   Page\\: ↑/↓/←/→ Space Home End\n-   Fullscreen\\: F\n-   Overview\\: -/+\n-   Speaker Note\\: N\n-   Grid Background\\: Enter\n\n:::note\n## Note here\n:::\n<slide :class=\"size-50\">\n\n##  :fa-heart-o: CSS Syntax\n\nWebSlides is so easy to understand and love. Baseline\\: 8. {.text-intro}\n\n* :Typography\\::{.text-label}  .text-landing, .text-subtitle, .text-data, .text-intro...\n* :BG Colors\\::{.text-label}  .bg-primary, .bg-blue,.bg-apple...\n* :BG Images\\::{.text-label} .background, .background-center-bottom...\n* :Sizes\\::{.text-label} .size-50, .size-40...\n* :Component\\::{.text-label} card, flexblock, gallery...\n* :Animation\\::{.text-label} autoplay, animate.css...\n{.description}\n\n<slide :class=\"size-50 aligncenter\">\n\n## Text Classes\n\n<slide class=\"aligncenter\">\n# Landings {.text-landing}\n\n`.text-landing`\n\n<slide class=\"aligncenter\">\n# Landings {.text-landing}\n\nCreate a simple web presence. {.text-intro}\n\n`.text-intro`\n\n<slide class=\"aligncenter\">\n\nPOWERED BY [#WEBSLIDES](https://twitter.com/search?f=tweets&vertical=default&q=%23WebSlides&src=typd) `.text-subtitle` {.text-subtitle}\n\n# Landings {.text-landing}\n\nCreate a simple web presence. {.text-intro}\n\n`.text-intro`\n\n<slide class=\"bg-black aligncenter\" image=\"https://cn.bing.com/az/hprichbg/rb/RedAntarctica_EN-AU12197122155_1920x1080.jpg\">\n# **Landings** {.text-landing.text-shadow}\n\n`.text-shadow` {.text-intro}\n\n<slide class=\"bg-apple aligncenter\">\n## 4,235,678 {.text-data}\n\n`.text-data`\n\n<slide>\n\nWhy WebSlides? .text-context {.text-content}\n\n## WebSlides is incredibly easy and versatile. The easiest way to make HTML presentations.\n\n<slide>\n\n`.text-cols (2 columns)`\n\n:::div {.text-cols}\n\n**Why WebSlides?** There are excellent presentation tools out there. WebSlides is about sharing content, essential features, and clean markup. **Each parent &lt;slide&gt;**  in the #webslides element is an individual slide.\n\n**WebSlides help you build a culture of innovation and excellence**. When you're really passionate about your job, you can change the world. How to manage a design-driven organization? Leadership through usefulness, openness, empathy, and good taste.\n\n:::\n\n:::flexblock {.metrics}\n\n:fa-phone:\n\nCall us at 555.345.6789\n\n----\n\n:fa-twitter:\n\n@username\n\n----\n:fa-envelope:\nSend us an email\n:::\n\n<slide :class=\"size-50 aligncenter\">\n\n## Backgrounds\n\n<slide>\n## Corporate Backgrounds\n:::flexblock {.blink.border}\n\n## .bg-primary {..bg-primary}\n\n\\#44d\n\n---\n\n## .bg-secondary {..bg-secondary}\n\n\\#67d\n\n---\n\n## .bg-light {..bg-light}\n\n\\#edf2f7\n\n---\n\n## body\n\n\\#f7f9fb\n\n:::\n\n---\n\n## General Colors\n\n:::flexblock {.blink.border}\n\n## .bg-black {..bg-black}\n\n\\#111\n\n---\n\n## .bg-black-blue {..bg-black-blue}\n\n\\#123\n\n---\n\n## .bg-white {..bg-white}\n\n\\#fff\n:::\n\n<slide>\n\n## Colorful\n\n:::flexblock {.border.blink}\n\n## .bg-red {..bg-red}\n\n\\#c23\n\n---\n\n## .bg-green {..bg-green}\n\n\\#077\n\n---\n\n## .bg-blue {..bg-blue}\n\n\\#346\n\n---\n\n## .bg-purple {..bg-purple}\n\n\\#62b\n\n:::\n\n---\n\n### Transparent Backgrounds\n\n:::flexblock {.border.blink}\n\n## .bg-trans-dark {..bg-trans-dark}\n\nrgba(0, 0, 0, 0.5)\n\n---\n\n## .bg-trans-light {..bg-trans-light}\n\nrgba(255, 255, 255, 0.2)\n\n:::\n\n<slide class=\"bg-gradient-h\">\n# Gradients\n\n:::flexblock {.border}\n\nHorizontal\n`.bg-gradient-h`\n\n---\n\nRadial\n`.bg-gradient-r`\n\n---\n\nVertical\n`.bg-gradient-v`\n:::\n\n<slide class=\"bg-gradient-v aligncenter\">\n## Vertical Gradient\n\n`.bg-gradient-v`\n\n<slide class=\"bg-gradient-r aligncenter\">\n## Radial Gradient\n\n`.bg-gradient-r`\n\n<slide class=\"bg-black\" video=\"https://webslides.tv/static/videos/working.mp4 poster='https://webslides.tv/static/images/working.jpg'\" >\n\n`.background-video`\n\n## **WebSlides is the easiest way to make HTML presentations. Inspire and engage.**\n\n<slide class=\"bg-blue aligncenter\" video=\"https://webslides.tv/static/videos/working.mp4 poster='https://webslides.tv/static/images/working.jpg' .dark\">\n\n## BG Video with Overlay {.text-landing}\n\n`<slide class=\"bg-blue aligncenter\" video=\"https://webslides.tv/static/videos/working.mp4 poster='https://webslides.tv/static/images/working.jpg' .dark\">` or `.light`\n\n<slide class=\"fullscreen bg-blue\" youtube=\".dark id='_m67JbGjWnc' autoplay loop\" :class=\"aligncenter\">\n\n## **Youtube Background**\n\n`<slide youtube=\".dark id='_m67JbGjWnc' autoplay loop\">`\n\n<slide image=\"https://webslides.tv/static/images/iphone-hand.png .right-bottom\">\n\n:::{.content-left}\n\n### .background-(position)\n\n:::flexblock {.specs}\n::fa-wifi::\n\n## Ultra-Fast WiFi\n\nSimple and secure file sharing.\n\n---\n\n::fa-battery-full::\n\n## All day battery life\n\nYour battery worries may be over.\n\n---\n\n::fa-life-ring::\n\n## All day battery life\n\nWe'll fix it or if we can't, we'll replace it.\n\n:::\n\n<slide :class=\"size-50 aligncenter\">\n\n## Components\n\n<slide :class=\"size-60\">\n\n## Shadowbox\n\n`:::shadowbox`\n\n---\n\n:::shadowbox\n\n## We're web people.\n\nThere're excellent presentation tools out there. WebSlides is about telling the story, and sharing it in a beautiful way. Hypertext and clean code as narrative elements.\n\n---\n\n## Work better, faster.\n\nDesigners, marketers, and journalists can now focus on the content. Simply [choose a demo](https://webslides.tv/demos) and customize it in minutes.\n\n:::\n\n<slide :class=\"size-80\">\n\n:::card\n\n## Card\n\n.card-50.bg-white\n\n[Unsplash](http://Unsplash.com) is a really cool resource. It is a collection of Creative Commons Zero licensed photos that are really great. {.text-intro}\n\n-   :Role\\::{.text-label} Frontend\n-   :client\\::{.text-label} Acme\n-   :year\\::{.text-label} 2018\n    {.description}\n\n---\n\n![](https://source.unsplash.com/rCOWMC8qf8A/)\n\n:::\n\n<slide class=\"fullscreen\">\n\n:::card\n\n![](https://source.unsplash.com/ALtNa-uKy3M/)\n\n---\n\n## Bonsai\n\nBonsai is a Japanese art form using trees grown in containers — **.fullscreen > .card-50.** {.text-intro}\n\nSimilar practices exist in other cultures, including the Chinese tradition of penjing from which the art originated, and the miniature living landscapes of Vietnamese hòn non bộ.\n\n:::\n\n<slide class=\"aligncenter\">\n\n## **Flexible blocks**\n\n`:::flexblock` = Flexible blocks with auto-fill and equal height.\n\n<slide>\n\n## Flexblock\n\n:::flexblock\n\n## :fa-bar-chart: Purpose\n\nBusinesses that people love5\n\n---\n\n## :fa-bar-chart: Purpose\n\nBusinesses that people love6\n\n---\n\n## :fa-balance-scale: Purpose\n\nBusinesses that people love7\n\n---\n\n## :fa-cog: Purpose\n\nBusinesses that people love8\n\n:::\n\n---\n\n## Flexblock `{.blink.border}`\n\n:::flexblock {.blink.border}\n\n## :fa-bar-chart: Purpose\n\nBusinesses that people love1\n\n---\n\n## :fa-bar-chart: Purpose\n\nBusinesses that people love2\n\n---\n\n## :fa-balance-scale: Purpose\n\nBusinesses that people love3\n\n---\n\n## :fa-cog: Purpose\n\nBusinesses that people love4\n\n:::\n\n<slide>\n\n## Flexblock clients\n\n`{.clients}`\n\n:::flexblock {.clients}\n\n![](https://webslides.tv/static/images/logos/google.svg){.blacklogo}\n\n### Interfaces\n\nCollaboration with the Acme team to design their mobile apps.\n\n---\n\n![](https://webslides.tv/static/images/logos/microsoft.svg) {.blacklogo}\n\n### Interfaces\n\nCollaboration with the Acme team to design their mobile apps.\n\n---\n\n![](https://webslides.tv/static/images/logos/instagram.svg){.blacklogo}\n\n### Interfaces\n\nCollaboration with the Acme team to design their mobile apps.\n\n---\n\n![](https://webslides.tv/static/images/logos/netflix.svg){.blacklogo}\n\n### Interfaces\n\nCollaboration with the Acme team to design their mobile apps.\n\n:::\n\n<slide :class=\"size-70\">\n### Gallery\n\n`:::gallery`\n\n:::gallery\n\n![](https://source.unsplash.com/uPGOEbjbVGA/800x600)\n\n## Alicia Jiménez\n\nFounder & CEO\n\n---\n\n![](https://source.unsplash.com/6anudmpILw4/800x600)\n\n## Sam Trololovitz\n\nMaster of nothing\n\n---\n\n![](https://source.unsplash.com/IFxjDdqK_0U/800x600)\n\n## Erin Gustafson\n\nVP of Design\n\n:::\n\n<slide :class=\"size-80\">\n### Gallery overlay\n`:::gallery-overlay`\n\n:::gallery-overlay\n\n![](https://source.unsplash.com/uPGOEbjbVGA/800x600)\n\n## Alicia Jiménez\n\nFounder & CEO\n\n---\n\n![](https://source.unsplash.com/zhkTCCmD4xI/800x600)\n\n## Sam Trololovitz\n\nCTO\n\n---\n\n![](https://source.unsplash.com/IFxjDdqK_0U/800x600)\n\n## Erin Gustafson\n\nVP of Design\n\n:::\n\n<slide class=\"bg-red frame\">\n\n:::cta\n\n::^\\$^40::\n\n---\n\n## Watch TV shows anytime, anywhere\n\n.frame.bg-red\n\n:::\n\n\n<slide class=\"bg-black-blue\">\n## Grid Columns\n:::column\n### **:fa-line-chart: Design**\n\nDesign for growth. We've built a team of world-class designers, developers, and managers.\n\n---\n### **:fa-film: Videos**\n\nWe connect your audience needs, business goals, and brand values into a strategy.\n\n---\n### **:fa-users: Users**\n\nWe offer personalized services with deep expertise in design and technology.\n\n---\n### **:fa-graduation-cap: Teams**\n\nWe train teams to help organizations succeed in the digital age.\n:::\n\n<slide :class=\"size-50 aligncenter\">\n\n## Animations\n\n<slide :class=\"aligncenter\">\n\n## Autoplay Animation\n\n`.animated.lightSpeedIn.slow` {.lightSpeedIn.animated.slow}\n\n<slide :class=\"size-50\">\n### **animate.css** + `.build`\n\n---\n\n1. **fadeIn**{.bounce}\n2. **swing**{.swing}\n3. **flash**{.flash}\n4. **pulse**{.pulse}\n5. **shake**{.shake}\n6. **bounceIn**{.bounceIn}\n7. **wobble**{.wobble}\n8. **fadeInLeft**{.fadeInLeft}\n9. **flipInX**{.flipInX}\n10. **tada**{.tada}\n11. **slideInUp**{.slideInUp}\n12. **jello**{.jello}\n13. **heartBeat**{.heartBeat}\n14. **fadeInUp**{.fadeInUp}\n15. **lightSpeedIn**{.lightSpeedIn}\n    {.text-cols.build}\n\n<slide class=\"bg-black aligncenter\" image=\"https://source.unsplash.com/n9WPPWiPPJw/ .anim\">\n\n## .background.anim\n\n<slide :class=\"size-50 aligncenter\">\n\n## Content Position\n\n<slide class=\"slide-top\">\n:::{.content-left}\n### 1/9 left top\nPut content wherever you want. Have less. Do more. Create beautiful solutions.\n\n`.slide-top and .content-left`\n\n\n<slide class=\"slide-top\">\n:::{.content-center}\n### 2/9 center top\nIn a village of La Mancha, the name of which I have no desire to call to mind,\n\n`.slide-top and .content-center`\n\n<slide class=\"slide-top\">\n:::{.content-right}\n### 3/9 right top\nthere lived not long since one of those gentlemen that keep a lance in the lance-rack, an old buckler, a lean hack, and a greyhound for coursing.\n\n`.slide-top and .content-right`\n\n\n<slide>\n:::{.content-left}\n### 4/9 left top\nAn olla of rather more beef than mutton, a salad on most nights, scraps on Saturdays,\n\n`.content-left`\n\n<slide>\n:::{.content-center}\n### 5/9 center top\nlentils on Fridays, and a pigeon or so extra on Sundays, made away with three-quarters of his income.\n\n`.content-center`\n\n<slide>\n:::{.content-right}\n### 6/9 right top\nhe rest of it went in a doublet of fine cloth and velvet breeches and shoes to match for holidays,\n\n`.content-right`\n\n\n<slide class=\"slide-bottom\">\n:::{.content-left}\n### 7/9 left bottom\nwhile on week-days he made a brave figure in his best homespun.\n\n`.slide-bottom` and `.content-left`\n\n\n<slide class=\"slide-bottom\">\n:::{.content-center}\n### 8/9 center bottom\nHe had in his house a housekeeper past forty, a niece under twenty, and a lad for the field and market-place,\n\n`.slide-bottom` and `.content-center`\n\n<slide class=\"slide-bottom\">\n:::{.content-right}\n### 9/9 right bottom\nwho used to saddle the hack as well as handle the bill-hook.\n\n`.slide-bottom` and `.content-right`\n\n<slide class=\"bg-black slide-bottom\" image=\"https://source.unsplash.com/RSOxw9X-suY/\">\n\n:::div {.content-left}\n:fa-tree large:\n\n## 1,000,000\n### We're working to protect up to a million acres of sustainable forest.\n\n:::\n\n\n<slide :class=\"size-50 aligncenter\">\n\n## Layout\n\n<slide :class=\"size-50\">\n\n### **What is Stendhal Syndrome?**\n\nBeauty overdose. `.text-pull-right` {.text-intro}\n\nImagine that you are in Florence. If you suddenly start to feel that you literally cannot breathe, you may be experiencing Stendhal Syndrome.\n\nPsychiatrists have long debated whether it really exists. {.text-pull-right}\n\nThe syndrome is not only associated with viewing a beautiful place, but also good art.\n\nThe beauty of Italian art has a concentrated perfection and transcendent sensuality that is incredibly addictive.\n\n\n<slide>\n\n:::{.aligncenter}\n### Simple CSS Alignments\n\nPut content wherever you want.\n:::\n\n:::footer\nFooter: logo, credits... (.alignleft) {.alignleft}\n\n[:fa-twitter: @username .alignright](){.alignright}\n\n:::\n\n:::header\nHeader (logo) :.alignright:{.alignright}\n:::\n\n\n<slide :class=\"size-80 aligncenter\">\n\n## Prismjs for Code Highlight\n\n<slide :class=\"size-80\">\n\n:::column {.vertical-align}\n### **WebSlides is really easy**\nEach parent `<section>` in the #webslides element is an individual slide. {.text-intro}\n\nCode is neat, scalable, and well documented. It uses **intuitive markup with popular naming conventions**. There's no need to overuse classes or nesting. **Based on** [SimpleSlides](https://github.com/jennschiffer/SimpleSlides) , by [Jenn Schiffer](http://jennmoney.biz) :)\n\n----\n```html\n<article id=\"webslides\">\n  <!-- Slide 1 -->\n  <section>\n    <h1>Design for trust</h1>\n  </section>\n  <!-- Slide 2 -->\n  <section class=\"bg-primary\">\n    <div class=\"wrap\">\n      <h2>.wrap = container (width: 90%) with fadein</h2>\n    </div>\n  </section>\n</article>\n```\n:::\n\n---\nVertical sliding? `<article id=\"webslides\" class=\"vertical\">` {.aligncenter}\n\n\n<slide :class=\"size-80\">\n\nOptional · 500+ icons {.text-subtitle}\n## [:fa-flag: Font Awesome](http://fontawesome.io/icons/) as **SVG icons** {.animated.bounceIn}\n\n```markdown\n:fa-flag:\n```\n\n\n<slide :class=\"size-50 aligncenter\">\n\n## Quote\n\n<slide class=\"bg-black-blue\" :class=\"size-60\">\n> I have always appreciated designers who dare to reinterpret fabrics and proportions, so I follow the Japanese and Belgian designers.\n> ==Zaha Hadid==\n> {.text-quote}\n\n<slide image=\"https://webslides.tv/static/images/satya.png .left-bottom\">\n\n:::div {.content-right}\n> \"There is something only a CEO uniquely can do, which is set that tone, which can then capture the soul of the collective.\"\n> ==Satya Nadella, CEO of Microsoft.==\n:::\n\n\n<slide>\n:::card {.quote}\n\n\n\n![](https://webslides.tv/static/images/davinci.png)\n\n---\n> “WebSlides helped us build a culture of innovation and excellence.”\n> ==Leonardo da Vinci==\n\n\n<slide class=\"aligncenter\">\n\n## Plugins\n\n<slide :class=\"size-60\">\n## echarts {.aligncenter}\n\n\n```echarts {style=\"height:100%;width:100%;\"}\n{\n    tooltip: {\n        trigger: 'item',\n        formatter: \"{a} <br/>{b}: {c} ({d}%)\"\n    },\n    legend: {\n        orient: 'vertical',\n        x: 'left',\n        data:['直达','营销广告','搜索引擎','邮件营销','联盟广告','视频广告','百度','谷歌','必应','其他']\n    },\n    series: [\n        {\n            name:'访问来源',\n            type:'pie',\n            selectedMode: 'single',\n            radius: [0, '30%'],\n\n            label: {\n                normal: {\n                    position: 'inner'\n                }\n            },\n            labelLine: {\n                normal: {\n                    show: false\n                }\n            },\n            data:[\n                {value:335, name:'直达', selected:true},\n                {value:679, name:'营销广告'},\n                {value:1548, name:'搜索引擎'}\n            ]\n        },\n        {\n            name:'访问来源',\n            type:'pie',\n            radius: ['40%', '55%'],\n            label: {\n                normal: {\n                    formatter: '{a|{a}}{abg|}\\n{hr|}\\n  {b|{b}：}{c}  {per|{d}%}  ',\n                    backgroundColor: '#eee',\n                    borderColor: '#aaa',\n                    borderWidth: 1,\n                    borderRadius: 4,\n                    // shadowBlur:3,\n                    // shadowOffsetX: 2,\n                    // shadowOffsetY: 2,\n                    // shadowColor: '#999',\n                    // padding: [0, 7],\n                    rich: {\n                        a: {\n                            color: '#999',\n                            lineHeight: 22,\n                            align: 'center'\n                        },\n                        // abg: {\n                        //     backgroundColor: '#333',\n                        //     width: '100%',\n                        //     align: 'right',\n                        //     height: 22,\n                        //     borderRadius: [4, 4, 0, 0]\n                        // },\n                        hr: {\n                            borderColor: '#aaa',\n                            width: '100%',\n                            borderWidth: 0.5,\n                            height: 0\n                        },\n                        b: {\n                            fontSize: 16,\n                            lineHeight: 33\n                        },\n                        per: {\n                            color: '#eee',\n                            backgroundColor: '#334455',\n                            padding: [2, 4],\n                            borderRadius: 2\n                        }\n                    }\n                }\n            },\n            data:[\n                {value:335, name:'直达'},\n                {value:310, name:'邮件营销'},\n                {value:234, name:'联盟广告'},\n                {value:135, name:'视频广告'},\n                {value:1048, name:'百度'},\n                {value:251, name:'谷歌'},\n                {value:147, name:'必应'},\n                {value:102, name:'其他'}\n            ]\n        }\n    ]\n}\n\n```\n\n<slide class=\"aligncenter\">\n\n## Plugins: mermaid\n\n<slide :class=\"size-60\">\n## Basic sequence diagram {.aligncenter}\n\n\n```mermaid\nsequenceDiagram\n    Alice ->> Bob: Hello Bob, how are you?\n    Bob-->>John: How about you John?\n    Bob--x Alice: I am good thanks!\n    Bob-x John: I am good thanks!\n    Note right of John: Bob thinks a long<br/>long time, so long<br/>that the text does<br/>not fit on a row.\n\n    Bob-->Alice: Checking with John...\n    Alice->John: Yes... John, how are you?\n```\n\n\n\n<slide :class=\"size-60\">\n\n## Message to self in loop {.aligncenter}\n\n```mermaid\nsequenceDiagram\n    participant Alice\n    participant Bob\n    Alice->>John: Hello John, how are you?\n    loop Healthcheck\n        John->>John: Fight against hypochondria\n    end\n    Note right of John: Rational thoughts<br/>prevail...\n    John-->>Alice: Great!\n    John->>Bob: How about you?\n    Bob-->>John: Jolly good!\n```\n\n<slide :class=\"size-80\">\n## Gantt {.aligncenter}\n\n\n```mermaid\ngantt\n       dateFormat  YYYY-MM-DD\n       title Adding GANTT diagram functionality to mermaid\n\n       section A section\n       Completed task            :done,    des1, 2014-01-06,2014-01-08\n       Active task               :active,  des2, 2014-01-09, 3d\n       Future task               :         des3, after des2, 5d\n       Future task2              :         des4, after des3, 5d\n\n       section Critical tasks\n       Completed task in the critical line :crit, done, 2014-01-06,24h\n       Implement parser and jison          :crit, done, after des1, 2d\n       Create tests for parser             :crit, active, 3d\n       Future task in critical line        :crit, 5d\n       Create tests for renderer           :2d\n       Add to mermaid                      :1d\n\n       section Documentation\n       Describe gantt syntax               :active, a1, after des1, 3d\n       Add gantt diagram to demo page      :after a1  , 20h\n       Add another diagram to demo page    :doc1, after a1  , 48h\n\n       section Last section\n       Describe gantt syntax               :after doc1, 3d\n       Add gantt diagram to demo page      :20h\n       Add another diagram to demo page    :48h\n\n```\n\n<slide :class=\"size-60\">\n\n## Flowchart support for fontawesome {.aligncenter}\n\n```mermaid\ngraph TD\n    B[\"fa:fa-twitter for peace\"]\n    B-->C[fa:fa-ban forbidden]\n    B-->D(fa:fa-spinner);\n    B-->E(A fa:fa-camera-retro perhaps?);\n```\n\n<slide class=\"aligncenter\">\n\n## Plugins: KaTex\n\n<slide class=\"bg-gradient-v\" :class=\"size-60\">\n\n## KaTex {.aligncenter}\n\n| equation                                                                                                                                                                  | description                                                                            |\n| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |\n| $\\nabla \\cdot \\vec{\\mathbf{B}}  = 0$                                                                                                                                      | divergence of $\\vec{\\mathbf{B}}$ is zero                                               |\n| $\\nabla \\times \\vec{\\mathbf{E}}\\, +\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{B}}}{\\partial t}  = \\vec{\\mathbf{0}}$                                                          | curl of $\\vec{\\mathbf{E}}$ is proportional to the rate of change of $\\vec{\\mathbf{B}}$ |\n| $\\nabla \\times \\vec{\\mathbf{B}} -\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{E}}}{\\partial t} = \\frac{4\\pi}{c}\\vec{\\mathbf{j}}    \\nabla \\cdot \\vec{\\mathbf{E}} = 4 \\pi \\rho$ | _wha?_                                                                                 |\n\n\n\n<slide :class=\"size-50 aligncenter\">\n\n## Others\n\n<slide :class=\"size-80\">\n\n::: {.content-left}\n## Button\n\n[.button](){.button} [.button.radius](){.button.radius}\n\n[.button.ghost](){.button.ghost} [:fa-github: svg-icon](){.button}\n:::\n\n::: {.content-left}\n## Avatar\n\n![](https://avatars2.githubusercontent.com/u/1073262?s=40&v=4){.avatar-40}\n![](https://avatars2.githubusercontent.com/u/1073262?s=40&v=4){.avatar-48}\n![](https://avatars2.githubusercontent.com/u/1073262?s=40&v=4){.avatar-56}\n![](https://avatars2.githubusercontent.com/u/1073262?s=40&v=4){.avatar-64}\n![](https://avatars2.githubusercontent.com/u/1073262?s=40&v=4){.avatar-72}\n![](https://avatars2.githubusercontent.com/u/1073262?s=40&v=4){.avatar-80}\n\n (80, 72, 64, 56, 48, and 40).\n:::\n\n<slide :class=\"aligncenter size-50\">\n\n## List\n\n----\n\n* Niubility！\n* WebSlides\n* Webpack build in\n* Markdown-it\n* Posthtml\n* Prismjs\n\n<slide :class=\"size-50\">\n## Table\n\n| Left-aligned | Center-aligned | Right-aligned |\n| :----------- | :------------: | ------------: |\n| git status   |   git status   |    git status |\n| git diff     |    git diff    |      git diff |\n| git status   |   git status   |    git status |\n\n\n<slide class=\"bg-purple\" :class=\"size-50 aligncenter\" image=\"http://h1.ioliu.cn/bing/SandiaSunrise_ZH-CN11155504388_1920x1080.jpg .dark\">\n\n## Speaker Mode\n\nClick **Url + [?mode=speaker](./?mode=speaker){.bg-primary style=\"font-size:120%\"}** to show Speaker Mode.\n\n<slide class=\"bg-primary\" :class=\"size-60 frame\">\n\n## View More Demos? {.text-serif.aligncenter}\n\n\\* \\* \\* {.text-symbols}\n\n<nav class=\"aligncenter\">\n* [:fa-th-large: Layout](./layout.html)\n* [:fa-tv: Background](./background.html)\n* [:fa-magic: Animation](./animation.html)\n* [:fa-cube: Component](./component.html)\n* [:fa-youtube: Media](./media.html)\n{.no-list-style}\n\n</nav>\n\n<slide class=\"bg-black-blue aligncenter\" image=\"https://cn.bing.com/az/hprichbg/rb/PragueChristmas_EN-AU8649790921_1920x1080.jpg .dark\">\n\n## U work so hard, **but** 干不过 write PPTs {.animated.tada}\n\n快使用 [nodeppt](https://github.com/ksky521/nodeppt) 轻松搞定高大上 PPT<br/> nodeppt 助力你的人生逆袭之路！ {.text-into.animated.delay-800.fadeIn}\n\n[:fa-cloud-download: Github](https://github.com/ksky521/nodeppt){.button.animated.delay-1s.fadeInUp}\n"
  },
  {
    "path": "site/layout.md",
    "content": "title: nodeppt Layout 布局演示\nspeaker: 三水清\nurl: https://github.com/ksky521/nodeppt\n\n<slide class=\"bg-black-blue aligncenter\" image=\"https://source.unsplash.com/C1HhAQrbykQ/ .dark\">\n\nLayout{.text-subtitle.animated.fadeInDown.delay-800}\n# nodeppt {.text-landing.text-shadow}\n\n这可能是迄今为止最好的网页版演示库 {.text-intro}\n\n[:fa-github: Github](https://github.com/ksky521/nodeppt){.button.ghost}\n\n\n<slide class=\"aligncenter\">\n\n# Content Position {.text-landing}\n\n<slide class=\"slide-top\">\n:::{.content-left}\n### 1/9 left top\nPut content wherever you want. Have less. Do more. Create beautiful solutions.\n\n`.slide-top and .content-left`\n\n\n<slide class=\"slide-top\">\n:::{.content-center}\n### 2/9 center top\nIn a village of La Mancha, the name of which I have no desire to call to mind,\n\n`.slide-top and .content-center`\n\n<slide class=\"slide-top\">\n:::{.content-right}\n### 3/9 right top\nthere lived not long since one of those gentlemen that keep a lance in the lance-rack, an old buckler, a lean hack, and a greyhound for coursing.\n\n`.slide-top and .content-right`\n\n\n<slide>\n:::{.content-left}\n### 4/9 left top\nAn olla of rather more beef than mutton, a salad on most nights, scraps on Saturdays,\n\n`.content-left`\n\n<slide>\n:::{.content-center}\n### 5/9 center top\nlentils on Fridays, and a pigeon or so extra on Sundays, made away with three-quarters of his income.\n\n`.content-center`\n\n<slide>\n:::{.content-right}\n### 6/9 right top\nhe rest of it went in a doublet of fine cloth and velvet breeches and shoes to match for holidays,\n\n`.content-right`\n\n\n<slide class=\"slide-bottom\">\n:::{.content-left}\n### 7/9 left bottom\nwhile on week-days he made a brave figure in his best homespun.\n\n`.slide-bottom` and `.content-left`\n\n\n<slide class=\"slide-bottom\">\n:::{.content-center}\n### 8/9 center bottom\nHe had in his house a housekeeper past forty, a niece under twenty, and a lad for the field and market-place,\n\n`.slide-bottom` and `.content-center`\n\n<slide class=\"slide-bottom\">\n:::{.content-right}\n### 9/9 right bottom\nwho used to saddle the hack as well as handle the bill-hook.\n\n`.slide-bottom` and `.content-right`\n\n\n<slide image=\"https://webslides.tv/static/images/satya.png .left-bottom\">\n\n:::div {.content-right}\n> \"There is something only a CEO uniquely can do, which is set that tone, which can then capture the soul of the collective.\"\n> ==Satya Nadella, CEO of Microsoft.==\n:::\n\n<slide image=\"https://webslides.tv/static/images/iphone-hand.png .right-bottom\">\n\n:::{.content-left}\n### .background-(position)\n\n:::flexblock {.specs}\n::fa-wifi::\n\n## Ultra-Fast WiFi\nSimple and secure file sharing.\n\n---\n::fa-battery-full::\n\n## All day battery life\nYour battery worries may be over.\n\n---\n::fa-life-ring::\n## All day battery life\nWe'll fix it or if we can't, we'll replace it.\n\n:::\n\n<slide :class=\"size-50\">\n\n### **What is Stendhal Syndrome?**\n\nBeauty overdose. `.text-pull-right` {.text-intro}\n\nImagine that you are in Florence. If you suddenly start to feel that you literally cannot breathe, you may be experiencing Stendhal Syndrome.\n\nPsychiatrists have long debated whether it really exists. {.text-pull-right}\n\nThe syndrome is not only associated with viewing a beautiful place, but also good art.\n\nThe beauty of Italian art has a concentrated perfection and transcendent sensuality that is incredibly addictive.\n\n\n\n<slide class=\"bg-primary\" :class=\"size-60 frame\">\n\n## View More Demos? {.text-serif.aligncenter}\n\n\\* \\* \\* {.text-symbols}\n\n<nav class=\"aligncenter\">\n* [:fa-tv: Background](./background.html)\n* [:fa-magic: Animation](./animation.html)\n* [:fa-cube: Component](./component.html)\n* [:fa-youtube: Media](./media.html)\n* [:fa-css3: Classes](./index.html)\n</nav>\n\n<slide class=\"aligncenter\">\n\n## U work so hard, **but** 干不过 write PPTs\n\n快使用 [nodeppt](https://github.com/ksky521/nodeppt) 轻松搞定高大上PPT<br/> nodeppt 助力你的人生逆袭之路！ {.text-into}\n\n[:fa-cloud-download: Github](https://github.com/ksky521/nodeppt){.button}\n"
  },
  {
    "path": "site/media.md",
    "content": "title: nodeppt 富媒体&插件演示\nspeaker: 三水清\nurl: https://github.com/ksky521/nodeppt\njs:\n    - https://www.echartsjs.com/asset/theme/infographic.js\nplugins:\n    - echarts: {theme: infographic}\n    - katex\n\n<slide class=\"bg-black-blue aligncenter\" image=\"https://source.unsplash.com/Zq_K89I9E-8/ .dark\">\n\nMedia & Plugin {.text-subtitle.animated.fadeInDown.delay-800}\n# nodeppt {.text-landing.text-shadow}\n\n这可能是迄今为止最好的网页版演示库 {.text-intro}\n\n[:fa-github: Github](https://github.com/ksky521/nodeppt){.button.ghost}\n\n\n<slide class=\"aligncenter\">\n\n## Media\n\n<slide class=\"bg-black\" video=\"https://webslides.tv/static/videos/working.mp4 poster='https://webslides.tv/static/images/working.jpg'\" >\n\n\n`.background-video`\n\n## **WebSlides is the easiest way to make HTML presentations. Inspire and engage.**\n\n<slide class=\"bg-blue aligncenter\" video=\"https://webslides.tv/static/videos/working.mp4 poster='https://webslides.tv/static/images/working.jpg' .dark\">\n\n\n## BG Video with Overlay {.text-landing}\n\n`<slide class=\"bg-blue aligncenter\" video=\"https://webslides.tv/static/videos/working.mp4 poster='https://webslides.tv/static/images/working.jpg' .dark\">` or `.light`\n\n\n<slide class=\"fullscreen bg-blue\" youtube=\".dark id='_m67JbGjWnc' autoplay loop\" :class=\"aligncenter\">\n\n## **Youtube Background**\n\n`<slide youtube=\".dark id='_m67JbGjWnc' autoplay loop\">`\n\n<slide class=\"aligncenter\">\n\n## Plugins\n\n\n<slide :class=\"size-60\">\n## echarts {.aligncenter}\n\n\n```echarts {style=\"height:100%;width:100%;\"}\n{\n    tooltip: {\n        trigger: 'item',\n        formatter: \"{a} <br/>{b}: {c} ({d}%)\"\n    },\n    legend: {\n        orient: 'vertical',\n        x: 'left',\n        data:['直达','营销广告','搜索引擎','邮件营销','联盟广告','视频广告','百度','谷歌','必应','其他']\n    },\n    series: [\n        {\n            name:'访问来源',\n            type:'pie',\n            selectedMode: 'single',\n            radius: [0, '30%'],\n\n            label: {\n                normal: {\n                    position: 'inner'\n                }\n            },\n            labelLine: {\n                normal: {\n                    show: false\n                }\n            },\n            data:[\n                {value:335, name:'直达', selected:true},\n                {value:679, name:'营销广告'},\n                {value:1548, name:'搜索引擎'}\n            ]\n        },\n        {\n            name:'访问来源',\n            type:'pie',\n            radius: ['40%', '55%'],\n            label: {\n                normal: {\n                    formatter: '{a|{a}}{abg|}\\n{hr|}\\n  {b|{b}：}{c}  {per|{d}%}  ',\n                    backgroundColor: '#eee',\n                    borderColor: '#aaa',\n                    borderWidth: 1,\n                    borderRadius: 4,\n                    // shadowBlur:3,\n                    // shadowOffsetX: 2,\n                    // shadowOffsetY: 2,\n                    // shadowColor: '#999',\n                    // padding: [0, 7],\n                    rich: {\n                        a: {\n                            color: '#999',\n                            lineHeight: 22,\n                            align: 'center'\n                        },\n                        // abg: {\n                        //     backgroundColor: '#333',\n                        //     width: '100%',\n                        //     align: 'right',\n                        //     height: 22,\n                        //     borderRadius: [4, 4, 0, 0]\n                        // },\n                        hr: {\n                            borderColor: '#aaa',\n                            width: '100%',\n                            borderWidth: 0.5,\n                            height: 0\n                        },\n                        b: {\n                            fontSize: 16,\n                            lineHeight: 33\n                        },\n                        per: {\n                            color: '#eee',\n                            backgroundColor: '#334455',\n                            padding: [2, 4],\n                            borderRadius: 2\n                        }\n                    }\n                }\n            },\n            data:[\n                {value:335, name:'直达'},\n                {value:310, name:'邮件营销'},\n                {value:234, name:'联盟广告'},\n                {value:135, name:'视频广告'},\n                {value:1048, name:'百度'},\n                {value:251, name:'谷歌'},\n                {value:147, name:'必应'},\n                {value:102, name:'其他'}\n            ]\n        }\n    ]\n}\n\n```\n<slide class=\"aligncenter\">\n\n## Plugins: mermaid\n\n<slide :class=\"size-60\">\n## Basic sequence diagram {.aligncenter}\n\n\n```mermaid\nsequenceDiagram\n    Alice ->> Bob: Hello Bob, how are you?\n    Bob-->>John: How about you John?\n    Bob--x Alice: I am good thanks!\n    Bob-x John: I am good thanks!\n    Note right of John: Bob thinks a long<br/>long time, so long<br/>that the text does<br/>not fit on a row.\n\n    Bob-->Alice: Checking with John...\n    Alice->John: Yes... John, how are you?\n```\n\n\n\n<slide :class=\"size-60\">\n\n## Message to self in loop {.aligncenter}\n\n```mermaid\nsequenceDiagram\n    participant Alice\n    participant Bob\n    Alice->>John: Hello John, how are you?\n    loop Healthcheck\n        John->>John: Fight against hypochondria\n    end\n    Note right of John: Rational thoughts<br/>prevail...\n    John-->>Alice: Great!\n    John->>Bob: How about you?\n    Bob-->>John: Jolly good!\n```\n\n<slide :class=\"size-80\">\n## Gantt {.aligncenter}\n\n\n```mermaid\ngantt\n       dateFormat  YYYY-MM-DD\n       title Adding GANTT diagram functionality to mermaid\n\n       section A section\n       Completed task            :done,    des1, 2014-01-06,2014-01-08\n       Active task               :active,  des2, 2014-01-09, 3d\n       Future task               :         des3, after des2, 5d\n       Future task2              :         des4, after des3, 5d\n\n       section Critical tasks\n       Completed task in the critical line :crit, done, 2014-01-06,24h\n       Implement parser and jison          :crit, done, after des1, 2d\n       Create tests for parser             :crit, active, 3d\n       Future task in critical line        :crit, 5d\n       Create tests for renderer           :2d\n       Add to mermaid                      :1d\n\n       section Documentation\n       Describe gantt syntax               :active, a1, after des1, 3d\n       Add gantt diagram to demo page      :after a1  , 20h\n       Add another diagram to demo page    :doc1, after a1  , 48h\n\n       section Last section\n       Describe gantt syntax               :after doc1, 3d\n       Add gantt diagram to demo page      :20h\n       Add another diagram to demo page    :48h\n\n```\n\n<slide :class=\"size-60\">\n\n## Flowchart support for fontawesome {.aligncenter}\n\n```mermaid\ngraph TD\n    B[\"fa:fa-twitter for peace\"]\n    B-->C[fa:fa-ban forbidden]\n    B-->D(fa:fa-spinner);\n    B-->E(A fa:fa-camera-retro perhaps?);\n```\n\n<slide class=\"aligncenter\">\n\n## Plugins: KaTex\n\n<slide class=\"bg-gradient-v\" :class=\"size-60\">\n\n## KaTex {.aligncenter}\n\n| equation                                                                                                                                                                  | description                                                                            |\n| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |\n| $\\nabla \\cdot \\vec{\\mathbf{B}}  = 0$                                                                                                                                      | divergence of $\\vec{\\mathbf{B}}$ is zero                                               |\n| $\\nabla \\times \\vec{\\mathbf{E}}\\, +\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{B}}}{\\partial t}  = \\vec{\\mathbf{0}}$                                                          | curl of $\\vec{\\mathbf{E}}$ is proportional to the rate of change of $\\vec{\\mathbf{B}}$ |\n| $\\nabla \\times \\vec{\\mathbf{B}} -\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{E}}}{\\partial t} = \\frac{4\\pi}{c}\\vec{\\mathbf{j}}    \\nabla \\cdot \\vec{\\mathbf{E}} = 4 \\pi \\rho$ | _wha?_                                                                                 |\n\n\n\n<slide class=\"bg-primary\" :class=\"size-60 frame\">\n\n## View More Demos? {.text-serif.aligncenter}\n\n\\* \\* \\* {.text-symbols}\n\n<nav class=\"aligncenter\">\n* [:fa-th-large: Layout](./layout.html)\n* [:fa-tv: Background](./background.html)\n* [:fa-magic: Animation](./animation.html)\n* [:fa-cube: Component](./component.html)\n* [:fa-css3: Classes](./index.html)\n</nav>\n\n<slide class=\"aligncenter\">\n\n## U work so hard, **but** 干不过 write PPTs\n\n快使用 [nodeppt](https://github.com/ksky521/nodeppt) 轻松搞定高大上PPT<br/> nodeppt 助力你的人生逆袭之路！ {.text-into}\n\n[:fa-cloud-download: Github](https://github.com/ksky521/nodeppt){.button}\n"
  },
  {
    "path": "site/mermaid.md",
    "content": "title: nodeppt - 这可能是迄今为止最好的网页版演示库\nspeaker: 三水清\nurl: https://github.com/ksky521/nodeppt\nplugins:\n    - mermaid: {theme: forest}\n\n\n<slide class=\"bg-black-blue aligncenter\" image=\"https://source.unsplash.com/Zq_K89I9E-8/ .dark\">\n\nmermaid {.text-subtitle.animated.fadeInDown.delay-800}\n# nodeppt {.text-landing.text-shadow}\n\n这可能是迄今为止最好的网页版演示库 {.text-intro}\n\n[:fa-github: Github](https://github.com/ksky521/nodeppt){.button.ghost}\n\n\n<slide class=\"aligncenter\">\n\n## mermaid\n\n\n<slide :class=\"size-60\">\n## Basic sequence diagram {.aligncenter}\n\n```mermaid\nsequenceDiagram\n    Alice ->> Bob: Hello Bob, how are you?\n    Bob-->>John: How about you John?\n    Bob--x Alice: I am good thanks!\n    Bob-x John: I am good thanks!\n    Note right of John: Bob thinks a long<br/>long time, so long<br/>that the text does<br/>not fit on a row.\n\n    Bob-->Alice: Checking with John...\n    Alice->John: Yes... John, how are you?\n```\n\n<slide :class=\"size-60\">\n## Mermaid can render state diagrams. {.aligncenter}\n\n```mermaid\nstateDiagram-v2\n    [*] --> Still\n    Still --> [*]\n\n    Still --> Moving\n    Moving --> Still\n    Moving --> Crash\n    Crash --> [*]\n```\n\n\n<slide :class=\"size-60\">\n\n## Message to self in loop {.aligncenter}\n\n```mermaid\nsequenceDiagram\n    participant Alice\n    participant Bob\n    Alice->>John: Hello John, how are you?\n    loop Healthcheck\n        John->>John: Fight against hypochondria\n    end\n    Note right of John: Rational thoughts<br/>prevail...\n    John-->>Alice: Great!\n    John->>Bob: How about you?\n    Bob-->>John: Jolly good!\n```\n\n<slide :class=\"size-80\">\n## Gantt {.aligncenter}\n\n\n```mermaid\ngantt\n       dateFormat  YYYY-MM-DD\n       title Adding GANTT diagram functionality to mermaid\n\n       section A section\n       Completed task            :done,    des1, 2014-01-06,2014-01-08\n       Active task               :active,  des2, 2014-01-09, 3d\n       Future task               :         des3, after des2, 5d\n       Future task2              :         des4, after des3, 5d\n\n       section Critical tasks\n       Completed task in the critical line :crit, done, 2014-01-06,24h\n       Implement parser and jison          :crit, done, after des1, 2d\n       Create tests for parser             :crit, active, 3d\n       Future task in critical line        :crit, 5d\n       Create tests for renderer           :2d\n       Add to mermaid                      :1d\n\n       section Documentation\n       Describe gantt syntax               :active, a1, after des1, 3d\n       Add gantt diagram to demo page      :after a1  , 20h\n       Add another diagram to demo page    :doc1, after a1  , 48h\n\n       section Last section\n       Describe gantt syntax               :after doc1, 3d\n       Add gantt diagram to demo page      :20h\n       Add another diagram to demo page    :48h\n\n```\n\n\n\n\n<slide :class=\"size-60\">\n\n## Flowchart support for fontawesome {.aligncenter}\n\n```mermaid\ngraph TD\n    B[\"fa:fa-twitter for peace\"]\n    B-->C[fa:fa-ban forbidden]\n    B-->D(fa:fa-spinner);\n    B-->E(A fa:fa-camera-retro perhaps?);\n```\n\n\n<slide class=\"bg-primary\" :class=\"size-60 frame\">\n\n## View More Demos? {.text-serif.aligncenter}\n\n\\* \\* \\* {.text-symbols}\n\n<nav class=\"aligncenter\">\n* [:fa-th-large: Layout](./layout.html)\n* [:fa-tv: Background](./background.html)\n* [:fa-magic: Animation](./animation.html)\n* [:fa-cube: Component](./component.html)\n* [:fa-css3: Classes](./index.html)\n</nav>\n\n<slide class=\"aligncenter\">\n\n## U work so hard, **but** 干不过 write PPTs\n\n快使用 [nodeppt](https://github.com/ksky521/nodeppt) 轻松搞定高大上PPT<br/> nodeppt 助力你的人生逆袭之路！ {.text-into}\n\n[:fa-cloud-download: Github](https://github.com/ksky521/nodeppt){.button}\n"
  },
  {
    "path": "site/public/background.js",
    "content": "(function() {\n    window.changeBackgroundColor = cls => {\n        const classList = wsInstance.currentSlide_.el.classList;\n        classList.forEach(a => {\n            if (/^bg-/.test(a)) {\n                classList.remove(a);\n            }\n        });\n        classList.add(cls);\n    };\n})();\n"
  }
]