[
  {
    "path": ".gitignore",
    "content": "_site\n.DS_Store\n\n"
  },
  {
    "path": "404.html",
    "content": "---\nlayout: default\ntitle: 404\nhide-in-nav: true\ndescription: \"这里什么都没有 :(\"\nheader-img: \"img/404-bg.jpg\"\npermalink: /404.html\n---\n\n\n<!-- Page Header -->\n{% include intro-header.html type=\"page\" short='true' %}\n\n<script>\n\tdocument.body.classList.add('page-fullscreen');\n</script>\n"
  },
  {
    "path": "Gruntfile.js",
    "content": "module.exports = function(grunt) {\n\n    // Project configuration.\n    grunt.initConfig({\n        pkg: grunt.file.readJSON('package.json'),\n        uglify: {\n            main: {\n                src: 'js/<%= pkg.name %>.js',\n                dest: 'js/<%= pkg.name %>.min.js'\n            }\n        },\n        less: {\n            expanded: {\n                options: {\n                    paths: [\"css\"]\n                },\n                files: {\n                    \"css/<%= pkg.name %>.css\": \"less/<%= pkg.name %>.less\"\n                }\n            },\n            minified: {\n                options: {\n                    paths: [\"css\"],\n                    cleancss: true\n                },\n                files: {\n                    \"css/<%= pkg.name %>.min.css\": \"less/<%= pkg.name %>.less\"\n                }\n            }\n        },\n        banner: '/*!\\n' +\n            ' * <%= pkg.title %> v<%= pkg.version %> (<%= pkg.homepage %>)\\n' +\n            ' * Copyright <%= grunt.template.today(\"yyyy\") %> <%= pkg.author %>\\n' +\n            ' */\\n',\n        usebanner: {\n            dist: {\n                options: {\n                    position: 'top',\n                    banner: '<%= banner %>'\n                },\n                files: {\n                    src: ['css/<%= pkg.name %>.css', 'css/<%= pkg.name %>.min.css', 'js/<%= pkg.name %>.min.js']\n                }\n            }\n        },\n        watch: {\n            scripts: {\n                files: ['js/<%= pkg.name %>.js'],\n                tasks: ['uglify'],\n                options: {\n                    spawn: false,\n                },\n            },\n            less: {\n                files: ['less/*.less'],\n                tasks: ['less'],\n                options: {\n                    spawn: false,\n                }\n            },\n        },\n    });\n\n    // Load the plugins.\n    grunt.loadNpmTasks('grunt-contrib-uglify');\n    grunt.loadNpmTasks('grunt-contrib-less');\n    grunt.loadNpmTasks('grunt-banner');\n    grunt.loadNpmTasks('grunt-contrib-watch');\n\n    // Default task(s).\n    grunt.registerTask('default', ['uglify', 'less', 'usebanner']);\n\n};\n"
  },
  {
    "path": "LICENSE",
    "content": "                                Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2015-2016 Huxpro\n\n       https://github.com/Huxpro/\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n\n=======================================================================\nHux Blog Subcomponents:\n\nThe Hux Blog project contains subcomponents with separate copyright\nnotices and license terms. Your use of the source code for the these\nsubcomponents is subject to the terms and conditions of the following\nlicenses.\n\n(MIT License) Clean Blog Jekyll Theme: https://github.com/BlackrockDigital/startbootstrap-clean-blog-jekyll/\nhttps://github.com/BlackrockDigital/startbootstrap-clean-blog-jekyll/blob/master/LICENSE\nCopyright (c) 2013-2016 Blackrock Digital LLC.\n"
  },
  {
    "path": "README.md",
    "content": "# 小马哥的技术博客 [https://mercyblitz.github.io/](https://mercyblitz.github.io/)\n\n欢迎来到小马哥的技术博客，这里将深入探讨相关技术，包括行业动态，架构设计，设计模式，框架使用，源码分析等。\n\n**博客将不定期更新，请小伙伴们随时关注哦！**\n\n\n\n\n\n\n## [我的文章](https://mercyblitz.github.io/archive/)\n\n\n知识星球：https://t.zsxq.com/72rj2rr\n\nSF : https://segmentfault.com/u/mercyblitz\n\n微信：**mercyblitz-1985**\n\n微博：**mercyblitz**\n\n\n## [我的课程](https://mercyblitz.github.io/lessons/)\n\n\n## [《Spring Boot 编程思想》](https://mercyblitz.github.io/books/thinking-in-spring-boot/)\n\n\n## [关于我](https://mercyblitz.github.io/about/)\n\n\n小马哥，十余年Java EE 从业经验，架构师、微服务布道师、Dubbo 维护者。目前主要负责阿里巴巴集团微服务技术实施、架构衍进、基础设施构建等。重点关注云计算、微服务以及软件架构等领域。通过SUN Java（SCJP、SCWCD、SCBCD）以及Oracle OCA 等的认证。\n\n"
  },
  {
    "path": "README.zh.md",
    "content": "# Hux blog 模板\n\n### [我的博客在这里 &rarr;](http://huxpro.github.io)\n\n\n### 关于收到\"Page Build Warning\"的email\n\n由于jekyll升级到3.0.x,对原来的pygments代码高亮不再支持，现只支持一种-rouge，所以你需要在 `_config.yml`文件中修改`highlighter: rouge`.另外还需要在`_config.yml`文件中加上`gems: [jekyll-paginate]`.\n\n同时,你需要更新你的本地jekyll环境.\n\n使用`jekyll server`的同学需要这样：\n\n1. `gem update jekyll` # 更新jekyll\n2. `gem update github-pages` #更新依赖的包\n\n使用`bundle exec jekyll server`的同学在更新jekyll后，需要输入`bundle update`来更新依赖的包.\n\n参考文档：[using jekyll with pages](https://help.github.com/articles/using-jekyll-with-pages/) & [Upgrading from 2.x to 3.x](http://jekyllrb.com/docs/upgrading/2-to-3/)\n\n\n## 关于模板(beta)\n\n我的博客仓库——`huxpro.github.io`，是经常修改的，而且还会有人乱提交代码，因此给大家做了一个稳定版的模板。大家可以直接fork模板——`huxblog-boilerplate`,要改的地方我都说明了。或者可以直接下载zip到本地自己去修改。\n\n```\n$ git clone git@github.com:Huxpro/huxblog-boilerplate.git\n```\n\n**[在这里预览模板 &rarr;](http://huangxuan.me/huxblog-boilerplate/)**\n\n## 各版本特性\n\n##### New Feature (V1.5.2)\n\n* 当你fork了我的仓库之后，还要删掉里面的关于我的文档是不是感到略烦躁呢？**Boilerplate** 模板将帮助你快速开始，方便合并与更新。\n* `-apple-system`被添加到了字体规则里面了，这套字体格式能将iOS9默认的新字体**San Francisco**表现的非常漂亮。\n* 解决了代码过长自动换行的bug,替换为横向滚动条。详情请见[issue#15](https://github.com/Huxpro/huxpro.github.io/issues/15)\n\n###### 其他历史版本个人觉得没有必要了解，看看英文就行了。\n\n\n\n## 支持\n\n* 你可以自由的fork。如果你能将主题作者和 github 的地址保留在你的页面底部，我将非常感谢你。\n* 如果你喜欢我的这个博客模板，请在`huxpro.github.io`这个repository点个赞——右上角**star**一下。\n\n## 说明文档\n\n* 开始\n\t* [环境要求](#environment)\n\t* [开始](#get-started)\n\t* [写一篇博文](#write-posts)\n* 组件\n\t* [侧边栏](#sidebar)\n\t* [迷你关于我](#mini-about-me)\n\t* [推荐标签](#featured-tags)\n\t* [好友链接](#friends)\n\t* [HTML5 演示文档布局](#keynote-layout)\n* 评论与 Google/Baidu Analytics\n\t* [评论](#comment)\n\t* [网站分析](#analytics) \n* 高级部分\n\t* [自定义](#customization)\n\t* [标题底图](#header-image)\n\t* [搜索展示标题-头文件](#seo-title)\n\n#### Environment\n\n如果你安装了jekyll，那你只需要在命令行输入`jekyll serve`就能在本地浏览器预览主题。你还可以输入`jekyll serve --watch`，这样可以边修改边自动运行修改后的文件。\n\n经 [@BrucZhaoR](https://github.com/BruceZhaoR)的测试，好像两个命令都是可以的自动运行修改后的文件的，刷新后可以实时预览。官方文件是建议安装bundler，这样你在本地的效果就跟在github上面是一样的。详情请见这里：https://help.github.com/articles/using-jekyll-with-pages/#installing-jekyll\n\n\n#### Get Started\n\n你可以通用修改 `_config.yml`文件来轻松的开始搭建自己的博客:\n\n```\n# Site settings\ntitle: Hux Blog             # 你的博客网站标题\nSEOTitle: Hux Blog\t\t\t# 在后面会详细谈到\ndescription: \"Cool Blog\"    # 随便说点，描述一下\n\n# SNS settings      \ngithub_username: huxpro     # 你的github账号\nweibo_username: huxpro      # 你的微博账号，底部链接会自动更新的。\n\n# Build settings\n# paginate: 10              # 一页你准备放几篇文章\n```\n\nJekyll官方网站还有很多的参数可以调，比如设置文章的链接形式...网址在这里：[Jekyll - Official Site](http://jekyllrb.com/) 中文版的在这里：[Jekyll中文](http://jekyllcn.com/).\n\n#### write-posts\n\n要发表的文章一般以markdown的格式放在这里`_posts/`，你只要看看这篇模板里的文章你就立刻明白该如何设置。\n\nyaml 头文件长这样:\n\n```\n---\nlayout:     post\ntitle:      \"Hello 2015\"\nsubtitle:   \"Hello World, Hello Blog\"\ndate:       2015-01-29 12:00:00\nauthor:     \"Hux\"\nheader-img: \"img/post-bg-2015.jpg\"\ntags:\n    - Life\n---\n\n```\n\n#### SideBar\n\n看右边:\n![](http://huangxuan.me/img/blog-sidebar.jpg)\n\n设置是在 `_config.yml`文件里面的`Sidebar settings`那块。\n```\n# Sidebar settings\nsidebar: true  #添加侧边栏\nsidebar-about-description: \"简单的描述一下你自己\"\nsidebar-avatar: /img/avatar-hux.jpg     #你的大头贴，请使用绝对地址.\n```\n\n侧边栏是响应式布局的，当屏幕尺寸小于992px的时候，侧边栏就会移动到底部。具体请见bootstrap栅格系统 <http://v3.bootcss.com/css/>\n\n\n#### Mini About Me\n\nMini-About-Me 这个模块将在你的头像下面，展示你所有的社交账号。这个也是响应式布局，当屏幕变小时候，会将其移动到页面底部，只不过会稍微有点小变化，具体请看代码。\n\n#### Featured Tags\n\n看到这个网站 [Medium](http://medium.com) 的推荐标签非常的炫酷，所以我将他加了进来。\n这个模块现在是独立的，可以呈现在所有页面，包括主页和发表的每一篇文章标题的头上。\n\n```\n# Featured Tags\nfeatured-tags: true  \nfeatured-condition-size: 1     # A tag will be featured if the size of it is more than this condition value\n```\n\n唯一需要注意的是`featured-condition-size`: 如果一个标签的 SIZE，也就是使用该标签的文章数大于上面设定的条件值，这个标签就会在首页上被推荐。\n \n内部有一个条件模板 `{% if tag[1].size > {{site.featured-condition-size}} %}` 是用来做筛选过滤的.\n\n\n#### Friends\n\n好友链接部分。这会在全部页面显示。\n\n设置是在 `_config.yml`文件里面的`Friends`那块，自己加吧。\n\n```\n# Friends\nfriends: [\n    {\n        title: \"Foo Blog\",\n        href: \"http://foo.github.io/\"\n    },\n    {\n        title: \"Bar Blog\",\n        href: \"http://bar.github.io\"\n    }\n]\n```\n\n\n#### Keynote Layout\n\nHTML5幻灯片的排版：\n\n![](http://huangxuan.me/img/blog-keynote.jpg)\n\n这部分是用于占用html格式的幻灯片的，一般用到的是 Reveal.js, Impress.js, Slides, Prezi 等等.我认为一个现代化的博客怎么能少了放html幻灯的功能呢~\n\n其主要原理是添加一个 `iframe`，在里面加入外部链接。你可以直接写到头文件里面去，详情请见下面的yaml头文件的写法。\n\n```\n---\nlayout:     keynote\niframe:     \"http://huangxuan.me/js-module-7day/\"\n---\n```\n\niframe在不同的设备中，将会自动的调整大小。保留内边距是为了让手机用户可以向下滑动，以及添加更多的内容。\n\n\n#### Comment\n\n博客不仅支持多说[Duoshuo](http://duoshuo.com)评论系统，也支持[Disqus](http://disqus.com)评论系统。\n\n`Disqus`优点是：国际比较流行，界面也很大气、简介，如果有人评论，还能实时通知，直接回复通知的邮件就行了；缺点是：评论必须要去注册一个disqus账号，分享一般只有Facebook和Twitter，另外在墙内加载速度略慢了一点。想要知道长啥样，可以看以前的版本点[这里](http://brucezhaor.github.io/about.html) 最下面就可以看到。\n\n`多说` 优点是：支持国内各主流社交软件(微博，微信，豆瓣，QQ空间 ...)一键分享按钮功能，另外登陆比较方便，管理界面也是纯中文的，相对于disqus全英文的要容易操作一些；缺点是：就是界面丑了一点。\n当然你是可以自定义界面的css的，详情请看多说开发者文档 http://dev.duoshuo.com/docs/5003ecd94cab3e7250000008 。\n\n**首先**，你需要去注册一个账号，不管是disqus还是多说的。**不要直接使用我的啊！**\n\n**其次**，你只需要在下面的yaml头文件中设置一下就可以了。\n\n```\nduoshuo_username: _你的用户名_\n# 或者\ndisqus_username: _你的用户名_\n```\n\n**最后**多说是支持分享的，如果你不想分享，请这样设置：`duoshuo_share: false`。你可以同时使用两个评论系统，不过个人感觉怪怪的。\n\n#### Analytics\n\n网站分析，现在支持百度统计和Google Analytics。需要去官方网站注册一下，然后将返回的code贴在下面：\n\n```\n# Baidu Analytics\nba_track_id: 4cc1f2d8f3067386cc5cdb626a202900\n\n# Google Analytics\nga_track_id: 'UA-49627206-1'            # 你用Google账号去注册一个就会给你一个这样的id\nga_domain: huangxuan.me\t\t\t# 默认的是 auto, 这里我是自定义了的域名，你如果没有自己的域名，需要改成auto。\n```\n\n#### Customization\n\n如果你喜欢折腾，你可以去自定义我的这个模板的 code，[Grunt](gruntjs.com)已经为你准备好了。（感谢 Clean Blog）\n\nJavaScript 的压缩混淆、Less 的编译、Apache 2.0 许可通告的添加与 watch 代码改动，这些任务都揽括其中。简单的在命令行中输入 `grunt` 就可以执行默认任务来帮你构建文件了。如果你想搞一搞 JavaScript 或 Less 的话，`grunt watch` 会帮助到你的。\n\n**如果你可以理解 `_include/` 和 `_layouts/`文件夹下的代码（这里是整个界面布局的地方），你就可以使用 Jekyll 使用的模版引擎 [Liquid](https://github.com/Shopify/liquid/wiki)的语法直接修改/添加代码，来进行更有创意的自定义界面啦！**\n\n#### Header Image\n\n标题底图是可以自己选的，看看几篇示例post你就知道如何设置了。在\n  [issue #6 ](https://github.com/Huxpro/huxpro.github.io/issues/6) 中我被问到：怎么样才能让标题底图好看呢？\n  \n标题底图的选取完全是看个人的审美了，我也帮不了你。每一篇文章可以有不同的底图，你想放什么就放什么，最后宽度要够，大小不要太大，否则加载慢啊。\n\n但是需要注意的是本模板的标题是**白色**的，所以背景色要设置为**灰色**或者**黑色**，总之深色系就对了。当然你还可以自定义修改字体颜色，总之，用github pages就是可以完全的个性定制自己的博客。\n\n#### SEO Title\n\n我的博客标题是 **“Hux Blog”** 但是我想要在搜索的时候显示 **“黄玄的博客 | Hux Blog”** ，这个就需要SEO Title来定义了。\n\n其实这个SEO Title就是定义了<head><title>标题</title></head>这个里面的东西和多说分享的标题，你可以自行修改的。\n\n## 致谢\n\n1. 这个模板是从这里[IronSummitMedia/startbootstrap-clean-blog-jekyll](https://github.com/IronSummitMedia/startbootstrap-clean-blog-jekyll)  fork 的。 感谢这个作者\n2. 感谢[@BrucZhaoR](https://github.com/BruceZhaoR)的中文翻译 \n\n3. 感谢 Jekyll、Github Pages 和 Bootstrap!\n\n\n\n"
  },
  {
    "path": "_config.yml",
    "content": "# Site settings\ntitle: Mercy Ma\nSEOTitle: 小马哥的技术博客\nheader-img: img/home-bg.jpg\nemail: mercyblitz@gmail.com\ndescription: \"关于程序、设计以及架构 | 小马哥，Javaer ，Software Engineer\"\nkeyword: \"小马哥, mercyblitz, Mercy Ma, 小马哥的技术博客\"\nurl: \"https://weibo.com/mercyblitz\"              # your host, for absolute URL\nbaseurl: \"\"                             # for example, '/blog' if your blog hosted on 'host/blog'\n\npost:\n  author: mercyblitz\n\n# Publish posts or collection documents with a future date.\nfuture: true\n\n# SNS settings\nRSS: true\nweibo_username:     mercyblitz\ngithub_username:    mercyblitz\ntwitter_username:   mercyblitz\n#facebook_username:  mercyblitz\n#linkedin_username:  firstname-lastname-idxxxx\n\n\n\n# Build settings\n# from 2016, 'pygments' is unsupported on GitHub Pages. Use 'rouge' for highlighting instead.\nhighlighter: rouge\npermalink: pretty\npaginate: 10\nexclude: [\"less\",\"node_modules\",\"Gruntfile.js\",\"package.json\",\"README.md\",\"README.zh.md\"]\nanchorjs: true                          # if you want to customize anchor. check out line:181 of `post.html`\n# If you have timezone issue (e.g. #68) in China, uncomment to use this:\n#timezone: CN  \n\n\n\n# Gems\n# from PR#40, to support local preview for Jekyll 3.0\n# make sure you have this gem installed\n# `$ gem install jekyll-paginate`\nplugins: [jekyll-paginate]\n\n\n\n# Markdown settings\n# replace redcarpet to kramdown,\n# although redcarpet can auto highlight code, the lack of header-id make the catalog impossible, so I switch to kramdown\n# document: http://jekyllrb.com/docs/configuration/#kramdown\nmarkdown: kramdown\nkramdown:\n  input: GFM                            # use Github Flavored Markdown !important\n  syntax_highlighter_opts:\n    span:\n      line_numbers: false\n    block:\n      line_numbers: true\n      start_line: 1\n\n\n\n# Disqus settings\ndisqus_username: mercyblitz\n\n# Netease settings\nnetease_comment: false\n\n\n\n# Analytics settings\n# Baidu Analytics \n# ba_track_id: [your track id]\n\n# Google Analytics\n# ga_track_id: TODO            # Format: UA-xxxxxx-xx\n# ga_domain: TODO\n\n\n\n# Sidebar settings\nsidebar: true                           # whether or not using Sidebar.\nsidebar-about-description: \"大家好，我是小马哥\"\nsidebar-avatar: /img/avatar-mecyblitz.jpg      # use absolute URL, seeing it's used in both `/` and `/about/`\n\n\n\n# Featured Tags\nfeatured-tags: true                     # whether or not using Feature-Tags\nfeatured-condition-size: 1              # A tag will be featured if the size of it is more than this condition value\n\n\n\n# Progressive Web Apps\nchrome-tab-theme-color: \"#000000\"\nservice-worker: true\n\n\n\n# MathJax rendering for layout:page (e.g. post preview)\npage-mathjax: false\n\n\n\n# Friends\nfriends: [\n    {}\n]\n"
  },
  {
    "path": "_includes/about/en.md",
    "content": "\n> Hard Code in Internet，Think Big Data too much\n\nHi, I'm Mercy Ma, as a member in [Apache Dubbo](https://dubbo.apache.org/) PPMC, an achitecture of [Spring Cloud Alibaba](https://github.com/spring-cloud-incubator/spring-cloud-alibaba), and working on Open Source projects and Micro Services infrastructure, certificated SUN Java（SCJP、SCWCD、SCBCD）, Oracle OCA and so on.\n\n\n## Books\n\n- 《Thinking in Spring Boot》（Chinese）\n\n\n## Online Courses\n\n- imooc\n    - [\bSpring Boot 2.0深度实践之核心技术篇](https://coding.imooc.com/class/252.html)\n    - Spring Boot 2.0深度实践之\b生态整合篇（即将上线...）\n- SegmentFault\n    - [Java 微服务实践 - Spring Boot / Spring Cloud](https://segmentfault.com/ls/1650000011387052)\n\n\n## Open Source Projects\n\n- Spring Cloud\n  - [Spring Cloud Alibaba](https://github.com/spring-cloud-incubator/spring-cloud-alibaba)\n- Apache\n    - [Apache Dubbo](https://github.com/apache/incubator-dubbo)\n    - [Apache Dubbo Spring Boot](https://github.com/apache/incubator-dubbo-spring-boot-project) \n- Alibaba\n    - [Alibaba Nacos](https://github.com/alibaba/nacos)\n        - [Nacos Spring](https://github.com/nacos-group/nacos-spring-project)\n        - [Nacos Spring Boot](https://github.com/nacos-group/nacos-spring-boot-project)\n    - [Alibaba Sentinel](https://github.com/alibaba/Sentinel)\n    - [Velocity Spring Boot Starter](https://github.com/alibaba/velocity-spring-boot-project)\n\n\n## Talks\n\n- Spring Cloud Alibaba 致力于提供分布式应用开发的一站式解决方法 · [CNCC 2018](http://cncc2018.ccf.org.cn/cms/show.action?code=publish_ff80808162f165f90163070bf87105de&siteid=100000&channelid=0000000002)\n- Dubbo Cloud Native 之路的实践与思考 · [Dubbo开发者沙龙（上海站）- 2018 Aliware技术行](https://www.itdks.com/eventlist/detail/2307)\n- Dubbo 的过去、现在以及未来 · [2017 国际互联网大会（上海）](http://2017.thegiac.com/)\n- 微服务实践之路（二） · [2016 SegmentFault 开发者大（杭州站）](https://segmentfault.com/sfdc-2016/hz)\n- 微服务实践之路（一） · [2016 厦门互联网技术峰会](https://www.bagevent.com/event/227489)"
  },
  {
    "path": "_includes/about/zh.md",
    "content": "> 手淫互联网，意淫大数据\n\n大家好，我是小马哥（mercyblitz），[Java 劝退师](https://www.douyu.com/mercyblitz)，[Apache Dubbo](https://dubbo.apache.org/) PMC、[Spring Cloud Alibaba](https://github.com/spring-cloud-incubator/spring-cloud-alibaba) 项目架构师，通过 SUN Java（SCJP、SCWCD、SCBCD）以及 Oracle OCA 等的认证。目前为自由职业者，承接企业培训、架构设计和咨询等业务。\n\n### [书籍出版](/my/books)\n\n### [线上课程](/my/lessons/)\n\n### [开源项目](/my/open-sources/)\n\n### [报告分享](/my/presentations/)\n\n### [企业培训](/my/training/)\n\n- 电子邮箱：mercyblitz@gmail.com\n- 微信：mercyblitz-1985\n- [微博](https://weibo.com/mercyblitz)：[@mercyblitz](https://weibo.com/mercyblitz)\n- [Github](http://github.com/mercyblitz)：[http://github.com/mercyblitz](http://github.com/mercyblitz)\n- [Twitter](https://twitter.com/mercyblitz)：[@mercyblitz](https://twitter.com/mercyblitz)\n- 微信公众号：**次灵均阁**\n![次灵均阁二维码](https://mercyblitz.github.io/books/thinking-in-spring-boot/assets/my_mp_qrcode.jpg)\n\n\n\n## 社区交流\n\n- 小马哥和小伙伴1号群（已满）\n- 小马哥和小伙伴2号群（已满）\n- 小马哥和小伙伴3号群\n![QQ 群](/img/qq_group_3.png)\n\n"
  },
  {
    "path": "_includes/featured-tags.html",
    "content": "{% comment %}\n    @param {boolean} bottom - bottom will render <hr> \n{% endcomment %}\n\n{% if site.featured-tags %}\n<section>\n    {% if include.bottom %}\n        <hr class=\"hidden-sm hidden-xs\">\n    {% endif %}\n    <h5><a href=\"{{'/archive/' | prepend: site.baseurl }}\">FEATURED TAGS</a></h5>\n    <div class=\"tags\">\n        {% capture tags %}\n        {% comment %}\n            there must be no space between for and if otherwise this tricky sort won't work.\n            url_encode/decode is for escaping otherwise extra <a> will get generated \n            but it will break sort...\n        {% endcomment %}\n        {% for tag in site.tags %}{% if tag[1].size > site.featured-condition-size %}\n                <a data-sort=\"{{ site.posts.size | minus: tag[1].size | prepend: '0000' | slice: -4, 4 }}\" \n                    href=\"{{ site.baseurl }}/archive/?tag={{ tag[0] | url_encode }}\"\n                    title=\"{{ tag[0] }}\"\n                    rel=\"{{ tag[1].size }}\">{{ tag[0] }}</a>\n        {% endif %}{% endfor %}\n        {% endcapture %}\n        {{ tags | split:'</a>' | sort | join:'</a>' }}\n    </div>\n</section>\n{% endif %}"
  },
  {
    "path": "_includes/footer.html",
    "content": "<!-- Footer -->\n<footer>\n    <div class=\"container\">\n        <div class=\"row\">\n            <div class=\"col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1\">\n                <!-- SNS Link -->\n                {% include sns-links.html center=true %}\n\n                <p class=\"copyright text-muted\">\n                    Copyright &copy; {{ site.title }} {{ site.time | date: '%Y' }}\n                    <br>\n                    Powered by <a href=\"http://huangxuan.me\">Hux Blog</a> |\n                    <iframe\n                        style=\"margin-left: 2px; margin-bottom:-5px;\"\n                        frameborder=\"0\" scrolling=\"0\" width=\"100px\" height=\"20px\"\n                        src=\"https://ghbtns.com/github-btn.html?user=huxpro&repo=huxpro.github.io&type=star&count=true\" >\n                    </iframe>\n                </p>\n            </div>\n        </div>\n    </div>\n</footer>\n\n<!-- jQuery -->\n<script src=\"{{ \"/js/jquery.min.js \" | prepend: site.baseurl }}\"></script>\n\n<!-- Bootstrap Core JavaScript -->\n<!-- Currently, only navbar scroll-down effect at desktop still depends on this -->\n<script src=\"{{ \"/js/bootstrap.min.js \" | prepend: site.baseurl }}\"></script>\n\n<!-- Custom Theme JavaScript -->\n<script src=\"{{ \"/js/hux-blog.min.js \" | prepend: site.baseurl }}\"></script>\n\n<!-- Service Worker -->\n{% if site.service-worker %}\n<script src=\"{{ \"/js/snackbar.js \" | prepend: site.baseurl }}\"></script>\n<script src=\"{{ \"/js/sw-registration.js \" | prepend: site.baseurl }}\"></script>\n{% endif %}\n\n<!-- async load function -->\n<script>\n    function async(u, c) {\n      var d = document, t = 'script',\n          o = d.createElement(t),\n          s = d.getElementsByTagName(t)[0];\n      o.src = u;\n      if (c) { o.addEventListener('load', function (e) { c(null, e); }, false); }\n      s.parentNode.insertBefore(o, s);\n    }\n</script>\n\n<!--\n     Because of the native support for backtick-style fenced code blocks\n     right within the Markdown is landed in Github Pages,\n     From V1.6, There is no need for Highlight.js,\n     so Huxblog drops it officially.\n\n     - https://github.com/blog/2100-github-pages-now-faster-and-simpler-with-jekyll-3-0\n     - https://help.github.com/articles/creating-and-highlighting-code-blocks/\n     - https://github.com/jneen/rouge/wiki/list-of-supported-languages-and-lexers\n-->\n<!--\n    <script>\n        async(\"http://cdn.bootcss.com/highlight.js/8.6/highlight.min.js\", function(){\n            hljs.initHighlightingOnLoad();\n        })\n    </script>\n    <link href=\"http://cdn.bootcss.com/highlight.js/8.6/styles/github.min.css\" rel=\"stylesheet\">\n-->\n\n\n\n{% if page.title == 'Archive' %}\n<!-- jquery.tagcloud.js -->\n<script>\n    async('{{ \"/js/jquery.tagcloud.js\" | prepend: site.baseurl }}',function(){\n        $.fn.tagcloud.defaults = {\n            //size: {start: 1, end: 1, unit: 'em'},\n            color: {start: '#bbbbee', end: '#2f93b4'},\n        };\n        $('#tag_cloud a').tagcloud();\n    })\n</script>\n<script src='{{ \"/js/archive.js \" | prepend: site.baseurl }}'></script>\n{% endif %}\n\n<!--fastClick.js -->\n<script>\n    async(\"//cdnjs.cloudflare.com/ajax/libs/fastclick/1.0.6/fastclick.min.js\", function(){\n        var $nav = document.querySelector(\"nav\");\n        if($nav) FastClick.attach($nav);\n    })\n</script>\n\n\n<!-- Google Analytics -->\n{% if site.ga_track_id %}\n<script>\n    // dynamic User by Hux\n    var _gaId = '{{ site.ga_track_id }}';\n    var _gaDomain = '{{ site.ga_domain }}';\n\n    // Originial\n    (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){\n    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),\n    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)\n    })(window,document,'script','//www.google-analytics.com/analytics.js','ga');\n\n    ga('create', _gaId, _gaDomain);\n    ga('send', 'pageview');\n</script>\n{% endif %}\n\n\n<!-- Baidu Tongji -->\n{% if site.ba_track_id %}\n<script>\n    // dynamic User by Hux\n    var _baId = '{{ site.ba_track_id }}';\n\n    // Originial\n    var _hmt = _hmt || [];\n    (function() {\n      var hm = document.createElement(\"script\");\n      hm.src = \"//hm.baidu.com/hm.js?\" + _baId;\n      var s = document.getElementsByTagName(\"script\")[0];\n      s.parentNode.insertBefore(hm, s);\n    })();\n</script>\n{% endif %}\n\n\n<!-- Side Catalog -->\n{% if page.catalog %}\n<script type=\"text/javascript\">\n    function generateCatalog (selector) {\n\n        // interop with multilangual \n        if ('{{ page.multilingual }}' == 'true') {\n            _containerSelector = 'div.post-container.active'\n        } else {\n            _containerSelector = 'div.post-container'\n        }\n\n        // init\n        var P = $(_containerSelector),a,n,t,l,i,c;\n        a = P.find('h1,h2,h3,h4,h5,h6');\n\n        // clean\n        $(selector).html('')\n\n        // appending\n        a.each(function () {\n            n = $(this).prop('tagName').toLowerCase();\n            i = \"#\"+$(this).prop('id');\n            t = $(this).text();\n            c = $('<a href=\"'+i+'\" rel=\"nofollow\">'+t+'</a>');\n            l = $('<li class=\"'+n+'_nav\"></li>').append(c);\n            $(selector).append(l);\n        });\n        return true;\n    }\n\n    generateCatalog(\".catalog-body\");\n\n    // toggle side catalog\n    $(\".catalog-toggle\").click((function(e){\n        e.preventDefault();\n        $('.side-catalog').toggleClass(\"fold\")\n    }))\n\n    /*\n     * Doc: https://github.com/davist11/jQuery-One-Page-Nav\n     * Fork by Hux to support padding\n     */\n    async(\"{{ '/js/jquery.nav.js' | prepend: site.baseurl }}\", function () {\n        $('.catalog-body').onePageNav({\n            currentClass: \"active\",\n            changeHash: !1,\n            easing: \"swing\",\n            filter: \"\",\n            scrollSpeed: 700,\n            scrollOffset: 0,\n            scrollThreshold: .2,\n            begin: null,\n            end: null,\n            scrollChange: null,\n            padding: 80\n        });\n    });\n</script>\n{% endif %}\n\n\n<!-- Multi-Lingual -->\n{% if page.multilingual %}\n<!-- Handle Language Change -->\n<script type=\"text/javascript\">\n    // get nodes\n    var $zh = document.querySelector(\".zh\");\n    var $en = document.querySelector(\".en\");\n    var $select = document.querySelector(\"select\");\n\n    // bind hashchange event\n    // Changes at v1.7.2: change the language flag from hash-basing to in-memory basing\n    // due to its confliction with catalog anchors.\n    // window.addEventListener('hashchange', _render);\n\n    // handle render\n    function _render(_hash){\n        var _hash = _hash || window.location.hash;\n        // en\n        if(_hash == \"#en\"){\n            $select.selectedIndex = 1;\n            $en.style.display = \"block\";\n            $en.classList.add(\"active\");\n            $zh.style.display = \"none\";\n            $zh.classList.remove(\"active\");\n        // zh by default\n        }else{\n            // not trigger onChange, otherwise cause a loop call.\n            $select.selectedIndex = 0;\n            $zh.style.display = \"block\";\n            $zh.classList.add(\"active\");\n            $en.style.display = \"none\";\n            $en.classList.remove(\"active\");\n        }\n        // interop with catalog \n        if (\"{{ page.catalog }}\") generateCatalog(\".catalog-body\");\n    }\n\n    // handle select change\n    function onLanChange(index){\n        if(index == 0){\n            _hash = \"#zh\"\n        }else{\n            _hash = \"#en\"\n        }\n        _render(_hash)\n    }\n\n    // init\n    _render();\n</script>\n{% endif %}\n"
  },
  {
    "path": "_includes/friends.html",
    "content": "{% if site.friends %}\n<hr>\n<h5>FRIENDS</h5>\n<ul class=\"list-inline\">\n  {% for friend in site.friends %}\n  <li><a href=\"{{friend.href}}\">{{friend.title}}</a></li>\n  {% endfor %}\n</ul>\n{% endif %}"
  },
  {
    "path": "_includes/head.html",
    "content": "<head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"google-site-verification\" content=\"xBT4GhYoi5qRD5tr338pgPM5OWHHIDR6mNg1a3euekI\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, viewport-fit=cover\">\n    <meta name=\"description\" content=\"{{ site.description }}\">\n    <meta name=\"keywords\"  content=\"{{ site.keyword }}\">\n    <meta name=\"theme-color\" content=\"{{ site.chrome-tab-theme-color }}\">\n    \n    <!-- Open Graph -->\n    <meta property=\"og:title\" content=\"{% if page.title %}{{ page.title }} - {{ site.SEOTitle }}{% else %}{{ site.SEOTitle }}{% endif %}\">\n    {% case page.layout %}\n    {% when 'post' %}\n    <meta property=\"og:type\" content=\"article\">\n    <meta property=\"og:description\" content=\"{{ page.excerpt | strip_html | truncate:200 }}\">\n    {% if page.date %}\n    <meta property=\"article:published_time\" content=\"{{ page.date | date: \"%Y-%m-%dT%H:%M:%SZ\" }}\">\n    {% endif %}\n    {% if page.author %}\n    <meta property=\"article:author\" content=\"{{ page.author }}\">\n    {% endif %}\n    {% for tag in page.tags %}\n    <meta property=\"article:tag\" content=\"{{ tag }}\">\n    {% endfor %}\n    {% else %}\n    <meta property=\"og:type\" content=\"website\">\n    <meta property=\"og:description\" content=\"{% if page.description %}{{ page.description }}{% else %}{{ site.description }}{% endif %}\">\n    {% endcase %}\n    <meta property=\"og:image\" content=\"{{ site.url }}{{ site.sidebar-avatar }}\">\n    <meta property=\"og:url\" content=\"{{ site.url }}{{ page.url }}\">\n    <meta property=\"og:site_name\" content=\"{{ site.SEOTitle }}\">\n    \n    <title>{% if page.title %}{{ page.title }} - {{ site.SEOTitle }}{% else %}{{ site.SEOTitle }}{% endif %}</title>\n\n    <!-- Web App Manifest -->\n    <link rel=\"manifest\" href=\"{{ site.baseurl }}/pwa/manifest.json\">\n\n    <!-- Favicon -->\n    <link rel=\"shortcut icon\" href=\"{{ site.baseurl }}/img/favicon.ico\">\n    \n    <!-- Canonical URL -->\n    <link rel=\"canonical\" href=\"{{ page.url | replace:'index.html','' | prepend: site.baseurl | prepend: site.url }}\">\n\n    <!-- Bootstrap Core CSS -->\n    <link rel=\"stylesheet\" href=\"{{ \"/css/bootstrap.min.css\" | prepend: site.baseurl }}\">\n\n    <!-- Custom CSS -->\n    <link rel=\"stylesheet\" href=\"{{ \"/css/hux-blog.min.css\" | prepend: site.baseurl }}\">\n\n    <!-- Custom Fonts -->\n    <!-- <link href=\"http://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css\" rel=\"stylesheet\" type=\"text/css\"> -->\n    <!-- Hux change font-awesome CDN to qiniu -->\n    <link href=\"//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/css/font-awesome.min.css\" rel=\"stylesheet\" type=\"text/css\">\n\n\n    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->\n    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->\n    <!--[if lt IE 9]>\n        <script src=\"https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js\"></script>\n        <script src=\"https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js\"></script>\n    <![endif]-->\n\n    <!-- ga & ba script hoook -->\n    <script></script>\n</head>\n"
  },
  {
    "path": "_includes/intro-header.html",
    "content": "{% comment %}\n    @param {string} type - 'page' | 'post' | 'keynote'\n    @param {boolean} short\n{% endcomment %}\n\n{% if include.type == 'post' %}\n<style type=\"text/css\">\n    header.intro-header{\n        position: relative;\n        background-image: url('{{ site.baseurl }}/{% if page.header-img %}{{ page.header-img }}{% else %}{{ site.header-img }}{% endif %}');\n        background: {{ page.header-bg-css }};\n    }\n\n    {% if page.header-mask %}\n    header.intro-header .header-mask{\n        width: 100%;\n        height: 100%;\n        position: absolute;\n        background: rgba(0,0,0, {{ page.header-mask }});\n    }\n    {% endif %}\n</style>\n{% if page.header-style == 'text' %}\n<header class=\"intro-header style-text\" >\n{% else %}\n<header class=\"intro-header\" >\n{% endif %}\n    <div class=\"header-mask\"></div>\n    {% if page.header-img-credit %}\n    <div class=\"header-img-credit\">\n        Image by <a href=\"//{{page.header-img-credit-href}}\">{{page.header-img-credit}}</a>\n    </div>\n    {% endif %}\n    <div class=\"container\">\n        <div class=\"row\">\n            <div class=\"col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1\">\n                <div class=\"post-heading\">\n                    <div class=\"tags\">\n                        {% for tag in page.tags %}\n                        <a class=\"tag\" href=\"{{ site.baseurl }}/archive/?tag={{ tag | url_encode }}\" title=\"{{ tag }}\">{{ tag }}</a>\n                        {% endfor %}\n                    </div>\n                    <h1>{{ page.title }}</h1>\n                    {% comment %} always create a h2 for keeping the margin {% endcomment %}\n                    <h2 class=\"subheading\">{{ page.subtitle }}</h2>\n                    <span class=\"meta\">Posted by {% if page.author %}{{ page.author }}{% else %}{{ site.title }}{% endif %} on {{ page.date | date: \"%B %-d, %Y\" }}</span>\n                </div>\n            </div>\n        </div>\n    </div>\n</header>\n{% endif %}\n\n{% if include.type == 'keynote' %}\n<style type=\"text/css\">\n    header.intro-header{\n        height: 500px;\n        overflow: hidden;\n    }\n    header.intro-header .container{\n        visibility: hidden; \n    }\n    header iframe{\n        width: 100%;\n        height: 100%;\n        border: 0;\n    }\n</style>\n<header class=\"intro-header\" >\n    <iframe src=\"{{page.iframe}}\"></iframe>\n    <!-- keep for SEO -->\n    <div class=\"container\">\n        <div class=\"row\">\n            <div class=\"col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1\">\n                <div class=\"post-heading\">\n                    <div class=\"tags\">\n                        {% for tag in page.tags %}\n                        <a class=\"tag\" href=\"{{ site.baseurl }}/archive/?tag={{ tag | url_encode }}\" title=\"{{ tag }}\">{{ tag }}</a>\n                        {% endfor %}\n                    </div>\n                    <h1>{{ page.title }}</h1>\n                    {% comment %} always create a h2 for keeping the margin {% endcomment %}\n                    <h2 class=\"subheading\">{{ page.subtitle }}</h2>\n                    <span class=\"meta\">Posted by {% if page.author %}{{ page.author }}{% else %}{{ site.title }}{% endif %}\n                        on {{ page.date | date: \"%B %-d, %Y\" }}</span>\n                </div>\n            </div>\n        </div>\n    </div>\n</header>\n{% endif %}\n\n{% if include.type == 'page' %}\n<header class=\"intro-header\" style=\"background-image: url('{{ site.baseurl }}/{% if page.header-img %}{{ page.header-img }}{% else %}{{ site.header-img }}{% endif %}')\">\n  <div class=\"container\">\n    <div class=\"row\">\n      <div class=\"col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1\">\n        {% if include.short %}\n        <div class=\"site-heading\" id=\"tag-heading\">\n        {% else %}\n        <div class=\"site-heading\">\n        {% endif %}\n          <h1>{% if page.title %}{{ page.title }}{% else %}{{ site.title }}{% endif %}</h1>\n          <span class=\"subheading\">{{ page.description }}</span>\n        </div>\n      </div>\n    </div>\n  </div>\n</header>\n{% endif %}"
  },
  {
    "path": "_includes/mathjax_support.html",
    "content": "<script type=\"text/x-mathjax-config\">\n  MathJax.Hub.Config({\n    TeX: {\n      equationNumbers: {\n        autoNumber: \"AMS\"\n      }\n    },\n    SVG: {\n      scale: 90\n    },\n    tex2jax: {\n      inlineMath: [ ['$','$'] ],\n      displayMath: [ ['$$','$$'] ],\n      processEscapes: true,\n    }\n  });\n</script>\n<script type=\"text/javascript\"\n        src=\"https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_SVG\">\n</script>"
  },
  {
    "path": "_includes/nav.html",
    "content": "<!-- Navigation -->\n{% if page.nav-style == \"invert\" or page.header-style == \"text\" %}\n<nav class=\"navbar navbar-default navbar-custom navbar-fixed-top invert\">\n{% else %}\n<nav class=\"navbar navbar-default navbar-custom navbar-fixed-top\">\n{% endif %}\n    <div class=\"container-fluid\">\n        <!-- Brand and toggle get grouped for better mobile display -->\n        <div class=\"navbar-header page-scroll\">\n            <button type=\"button\" class=\"navbar-toggle\">\n                <span class=\"sr-only\">Toggle navigation</span>\n                <span class=\"icon-bar\"></span>\n                <span class=\"icon-bar\"></span>\n                <span class=\"icon-bar\"></span>\n            </button>\n            <a class=\"navbar-brand\" href=\"{{ site.baseurl }}/\">{{ site.title }}</a>\n        </div>\n\n        <!-- Collect the nav links, forms, and other content for toggling -->\n        <div id=\"huxblog_navbar\">\n            <div class=\"navbar-collapse\">\n                <ul class=\"nav navbar-nav navbar-right\">\n                    <li>\n                        <a href=\"{{ site.baseurl }}/\">首页</a>\n                    </li>\n                    <li>\n                        <a href=\"{{ site.baseurl }}/archive/\">我的文档</a>\n                    </li>\n                    <li>\n                        <a href=\"{{ site.baseurl }}/my/books/\">我的书籍</a>\n                    </li>\n                    <li>\n                        <a href=\"{{ site.baseurl }}/my/lessons/\">我的课程</a>\n                    </li>\n                    <li>\n                        <a href=\"{{ site.baseurl }}/my/presentations/\">我的演讲</a>\n                    </li>\n                    <li>\n                        <a href=\"{{ site.baseurl }}/my/open-sources/\">开源项目</a>\n                    </li>\n                    <li>\n                        <a href=\"{{ site.baseurl }}/about/\">关于我</a>\n                    </li>\n                </ul>\n            </div>\n        </div>\n        <!-- /.navbar-collapse -->\n    </div>\n    <!-- /.container -->\n</nav>\n<script>\n    // Drop Bootstarp low-performance Navbar\n    // Use customize navbar with high-quality material design animation\n    // in high-perf jank-free CSS3 implementation\n    var $body   = document.body;\n    var $toggle = document.querySelector('.navbar-toggle');\n    var $navbar = document.querySelector('#huxblog_navbar');\n    var $collapse = document.querySelector('.navbar-collapse');\n    var __HuxNav__ = {\n        close: function(){\n            $navbar.className = \" \";\n            // wait until animation end.\n            setTimeout(function(){\n                // prevent frequently toggle\n                if($navbar.className.indexOf('in') < 0) {\n                    $collapse.style.height = \"0px\"\n                }\n            },400)\n        },\n        open: function(){\n            $collapse.style.height = \"auto\"\n            $navbar.className += \" in\";\n        }\n    }\n    // Bind Event\n    $toggle.addEventListener('click', function(e){\n        if ($navbar.className.indexOf('in') > 0) {\n            __HuxNav__.close()\n        }else{\n            __HuxNav__.open()\n        }\n    })\n    /**\n     * Since Fastclick is used to delegate 'touchstart' globally\n     * to hack 300ms delay in iOS by performing a fake 'click',\n     * Using 'e.stopPropagation' to stop 'touchstart' event from \n     * $toggle/$collapse will break global delegation.\n     * \n     * Instead, we use a 'e.target' filter to prevent handler\n     * added to document close HuxNav.  \n     *\n     * Also, we use 'click' instead of 'touchstart' as compromise\n     */\n    document.addEventListener('click', function(e){\n        if(e.target == $toggle) return;\n        if(e.target.className == 'icon-bar') return;\n        __HuxNav__.close();\n    })\n</script>\n"
  },
  {
    "path": "_includes/short-about.html",
    "content": "<section class=\"visible-md visible-lg\">\n  <hr>\n  <h5><a href=\"{{'/about/' | prepend: site.baseurl }}\">ABOUT ME</a></h5>\n  <div class=\"short-about\">\n    {% if site.sidebar-avatar %}\n    <img src=\"{{site.sidebar-avatar}}\" />\n    {% endif %}\n    {% if site.sidebar-about-description %}\n    <p>{{site.sidebar-about-description}}</p>\n    {% endif %}\n    <!-- SNS Link -->\n    {% include sns-links.html %}\n  </div>\n</section>"
  },
  {
    "path": "_includes/sns-links.html",
    "content": "{% comment %}\n    @param {Boolean} center \n{% endcomment %}\n\n{% if include.center %}\n<ul class=\"list-inline text-center\">\n{% else %}\n<ul class=\"list-inline\">\n{% endif %}\n\n  {% if site.RSS %}\n  <li>\n    <a href=\"{{ \"/feed.xml\" | prepend: site.baseurl }}\">\n      <span class=\"fa-stack fa-lg\">\n        <i class=\"fa fa-circle fa-stack-2x\"></i>\n        <i class=\"fa fa-rss fa-stack-1x fa-inverse\"></i>\n      </span>\n    </a>\n  </li>\n  {% endif %}\n  {% if site.twitter_username %}\n  <li>\n    <a href=\"https://twitter.com/{{ site.twitter_username }}\">\n      <span class=\"fa-stack fa-lg\">\n        <i class=\"fa fa-circle fa-stack-2x\"></i>\n        <i class=\"fa fa-twitter fa-stack-1x fa-inverse\"></i>\n      </span>\n    </a>\n  </li>\n  {% endif %}\n  {% if site.zhihu_username %}\n  <li>\n    <a target=\"_blank\" href=\"https://www.zhihu.com/people/{{ site.zhihu_username }}\">\n      <span class=\"fa-stack fa-lg\">\n        <i class=\"fa fa-circle fa-stack-2x\"></i>\n        <i class=\"fa  fa-stack-1x fa-inverse\">知</i>\n      </span>\n    </a>\n  </li>\n  {% endif %}\n  {% if site.weibo_username %}\n  <li>\n    <a target=\"_blank\" href=\"http://weibo.com/{{ site.weibo_username }}\">\n      <span class=\"fa-stack fa-lg\">\n        <i class=\"fa fa-circle fa-stack-2x\"></i>\n        <i class=\"fa fa-weibo fa-stack-1x fa-inverse\"></i>\n      </span>\n    </a>\n  </li>\n  {% endif %}\n  {% if site.facebook_username %}\n  <li>\n    <a target=\"_blank\" href=\"https://www.facebook.com/{{ site.facebook_username }}\">\n      <span class=\"fa-stack fa-lg\">\n        <i class=\"fa fa-circle fa-stack-2x\"></i>\n        <i class=\"fa fa-facebook fa-stack-1x fa-inverse\"></i>\n      </span>\n    </a>\n  </li>\n  {% endif %}\n  {% if site.github_username %}\n  <li>\n    <a target=\"_blank\" href=\"https://github.com/{{ site.github_username }}\">\n      <span class=\"fa-stack fa-lg\">\n        <i class=\"fa fa-circle fa-stack-2x\"></i>\n        <i class=\"fa fa-github fa-stack-1x fa-inverse\"></i>\n      </span>\n    </a>\n  </li>\n  {% endif %}\n  {% if site.linkedin_username %}\n  <li>\n    <a target=\"_blank\" href=\"https://www.linkedin.com/in/{{ site.linkedin_username }}\">\n      <span class=\"fa-stack fa-lg\">\n        <i class=\"fa fa-circle fa-stack-2x\"></i>\n        <i class=\"fa fa-linkedin fa-stack-1x fa-inverse\"></i>\n      </span>\n    </a>\n  </li>\n  {% endif %}\n</ul>"
  },
  {
    "path": "_layouts/default.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n{% include head.html %}\n\n<!-- hack iOS CSS :active style -->\n<body ontouchstart=\"\">\n\n    {% include nav.html %}\n\n    {{ content }}\n\n    {% include footer.html %}\n\n\n<!-- Image to hack wechat -->\n<!-- <img src=\"/img/icon_wechat.png\" width=\"0\" height=\"0\" /> -->\n<!-- Migrate from head to bottom, no longer block render and still work -->\n\n</body>\n\n</html>\n"
  },
  {
    "path": "_layouts/keynote.html",
    "content": "---\nlayout: default\n---\n\n<!-- Image to hack wechat -->\n<!-- <img src=\"/img/icon_wechat.png\" width=\"0\" height=\"0\"> -->\n<!-- <img src=\"{{ site.baseurl }}/{% if page.header-img %}{{ page.header-img }}{% else %}{{ site.header-img }}{% endif %}\" width=\"0\" height=\"0\"> -->\n\n<!-- Post Header -->\n{% include intro-header.html type='keynote' %}\n\n\n<!-- Post Content -->\n<article>\n    <div class=\"container\">\n        <div class=\"row\">\n\n            <!-- Post Container -->\n            <div class=\"post-container\n                col-lg-8 col-lg-offset-2\n                col-md-10 col-md-offset-1 \">\n\n\t\t\t\t{{ content }}\n\n                <hr style=\"visibility: hidden;\">\n                <ul class=\"pager\">\n                    {% if page.previous.url %}\n                    <li class=\"previous\">\n                        <a href=\"{{ page.previous.url | prepend: site.baseurl | replace: '//', '/' }}\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"{{page.previous.title}}\">\n                        Previous<br>\n                        <span>{{page.previous.title}}</span>\n                        </a>\n                    </li>\n                    {% endif %}\n                    {% if page.next.url %}\n                    <li class=\"next\">\n                        <a href=\"{{ page.next.url | prepend: site.baseurl | replace: '//', '/' }}\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"{{page.next.title}}\">\n                        Next<br>\n                        <span>{{page.next.title}}</span>\n                        </a>\n                    </li>\n                    {% endif %}\n                </ul>\n                <hr style=\"visibility: hidden;\">\n\n                {% if site.disqus_username %}\n                <!-- disqus 评论框 start -->\n                <div class=\"comment\">\n                    <div id=\"disqus_thread\" class=\"disqus-thread\">\n\n                    </div>\n                </div>\n                <!-- disqus 评论框 end -->\n                {% endif %}\n\n            </div>\n\n            <!-- Sidebar Container -->\n            <div class=\"sidebar-container\n                col-lg-8 col-lg-offset-2\n                col-md-10 col-md-offset-1 \">\n\n                <!-- Featured Tags -->\n                {% include featured-tags.html %}\n\n                <!-- Friends Blog -->\n                {% include friends.html %}\n\n            </div>\n        </div>\n    </div>\n</article>\n\n<!-- resize header to fullscreen keynotes -->\n<script>\n    var $header = document.getElementsByTagName(\"header\")[0];\n    function resize(){\n        /*\n         * leave 85px to both\n         * - told/imply users that there has more content below\n         * - let user can scroll in mobile device, seeing the keynote-view is unscrollable\n         */\n        $header.style.height = (window.innerHeight-85) + 'px';\n    }\n    document.addEventListener('DOMContentLoaded', function(){\n        resize();\n    })\n    window.addEventListener('load', function(){\n        resize();\n    })\n    window.addEventListener('resize', function(){\n        resize();\n    })\n    resize();\n</script>\n\n\n{% if site.disqus_username %}\n<!-- disqus 公共JS代码 start (一个网页只需插入一次) -->\n<script type=\"text/javascript\">\n    /* * * CONFIGURATION VARIABLES * * */\n    var disqus_shortname = \"{{site.disqus_username}}\";\n    var disqus_identifier = \"{{page.id}}\";\n    var disqus_url = \"{{site.url}}{{page.url}}\";\n\n    (function() {\n        var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;\n        dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';\n        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);\n    })();\n</script>\n<!-- disqus 公共JS代码 end -->\n{% endif %}\n\n\n{% if site.anchorjs %}\n<!-- async load function -->\n<script>\n    function async(u, c) {\n      var d = document, t = 'script',\n          o = d.createElement(t),\n          s = d.getElementsByTagName(t)[0];\n      o.src = u;\n      if (c) { o.addEventListener('load', function (e) { c(null, e); }, false); }\n      s.parentNode.insertBefore(o, s);\n    }\n</script>\n<!-- anchor-js, Doc:http://bryanbraun.github.io/anchorjs/ -->\n<script>\n    async(\"//cdnjs.cloudflare.com/ajax/libs/anchor-js/1.1.1/anchor.min.js\",function(){\n        anchors.options = {\n          visible: 'always',\n          placement: 'right',\n          icon: '#'\n        };\n        anchors.add().remove('.intro-header h1').remove('.subheading').remove('.sidebar-container h5');\n    })\n</script>\n<style>\n    /* place left on bigger screen */\n    @media all and (min-width: 800px) {\n        .anchorjs-link{\n            position: absolute;\n            left: -0.75em;\n            font-size: 1.1em;\n            margin-top : -0.1em;\n        }\n    }\n</style>\n{% endif %}\n"
  },
  {
    "path": "_layouts/page.html",
    "content": "---\nlayout: default\n---\n\n<!-- Page Header -->\n{% include intro-header.html type='page' %}\n\n<!-- Main Content -->\n<div class=\"container\">\n\t<div class=\"row\">\n        {% if site.sidebar == false %}\n<!-- NO SIDEBAR -->\n    <!-- PostList Container -->\n            <div class=\"col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1 postlist-container\">\n                {{ content }}\n            </div>\n    <!-- Sidebar Container -->\n            <div class=\"\n                col-lg-8 col-lg-offset-2\n                col-md-10 col-md-offset-1\n                sidebar-container\">\n\n                <!-- Featured Tags -->\n                {% include featured-tags.html %}\n\n                <!-- Friends Blog -->\n                {% include friends.html %}\n            </div>\n        {% else %}\n\n<!-- USE SIDEBAR -->\n    <!-- PostList Container -->\n    \t\t<div class=\"\n                col-lg-8 col-lg-offset-1\n                col-md-8 col-md-offset-1\n                col-sm-12\n                col-xs-12\n                postlist-container\n            \">\n    \t\t\t{{ content }}\n    \t\t</div>\n    <!-- Sidebar Container -->\n            <div class=\"\n                col-lg-3 col-lg-offset-0\n                col-md-3 col-md-offset-0\n                col-sm-12\n                col-xs-12\n                sidebar-container\n            \">\n                <!-- Featured Tags -->\n                {% include featured-tags.html %}\n\n                <!-- Short About -->\n                {% include short-about.html %}\n\n                <!-- Friends Blog -->\n                {% include friends.html %}\n    \t\t</div>\n        {% endif %}\n\t</div>\n</div>\n\n{% if site.page-mathjax %}\n<!-- Add support for Mathjax by Voleking-->\n<!-- If you want to see formulars well in post preview, Maybe you should add this.-->\n<!-- However, most of the time formulars may not appear in the post preview, you can delete it.-->\n<script type=\"text/x-mathjax-config\">\n  MathJax.Hub.Config({\n    TeX: {\n      equationNumbers: {\n        autoNumber: \"AMS\"\n      }\n    },\n    tex2jax: {\n      inlineMath: [ ['$','$'] ],\n      displayMath: [ ['$$','$$'] ],\n      processEscapes: true,\n    }\n  });\n</script>\n<script type=\"text/javascript\"\n        src=\"https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_HTMLorMML\">\n</script>\n{% endif %}"
  },
  {
    "path": "_layouts/post.html",
    "content": "---\nlayout: default\n---\n\n<!-- Image to hack wechat -->\n<!-- <img src=\"/img/icon_wechat.png\" width=\"0\" height=\"0\"> -->\n<!-- <img src=\"{{ site.baseurl }}/{% if page.header-img %}{{ page.header-img }}{% else %}{{ site.header-img }}{% endif %}\" width=\"0\" height=\"0\"> -->\n\n<!-- Post Header -->\n{% include intro-header.html type='post' %}\n\n<!-- Post Content -->\n<article>\n    <div class=\"container\">\n        <div class=\"row\">\n\n    <!-- Post Container -->\n            <div class=\"\n                col-lg-8 col-lg-offset-2\n                col-md-10 col-md-offset-1\n                post-container\">\n\n                <!-- Multi-Lingual -->\n                {% if page.multilingual %}\n                <!-- Language Selector -->\n                <select class=\"sel-lang\" onchange= \"onLanChange(this.options[this.options.selectedIndex].value)\">\n                    <option value=\"0\" selected> 中文 Chinese </option>\n                    <option value=\"1\"> 英文 English </option>\n                </select>\n                {% endif %}\n\n\t\t\t\t{{ content }}\n\n                <hr style=\"visibility: hidden;\">\n                <ul class=\"pager\">\n                    {% if page.previous.url %}\n                    <li class=\"previous\">\n                        <a href=\"{{ page.previous.url | prepend: site.baseurl | replace: '//', '/' }}\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"{{page.previous.title}}\">\n                        Previous<br>\n                        <span>{{page.previous.title}}</span>\n                        </a>\n                    </li>\n                    {% endif %}\n                    {% if page.next.url %}\n                    <li class=\"next\">\n                        <a href=\"{{ page.next.url | prepend: site.baseurl | replace: '//', '/' }}\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"{{page.next.title}}\">\n                        Next<br>\n                        <span>{{page.next.title}}</span>\n                        </a>\n                    </li>\n                    {% endif %}\n                </ul>\n                <hr style=\"visibility: hidden;\">\n\n                {% if site.disqus_username %}\n                <!-- disqus 评论框 start -->\n                <div class=\"comment\">\n                    <div id=\"disqus_thread\" class=\"disqus-thread\"></div>\n                </div>\n                <!-- disqus 评论框 end -->\n                {% endif %}\n\n                {% if site.netease_comment %}\n                <!-- 网易云跟帖 评论框 start -->\n                <div id=\"cloud-tie-wrapper\" class=\"cloud-tie-wrapper\"></div>\n                <!-- 网易云跟帖 评论框 end -->\n                {% endif %}\n            </div>  \n\n    <!-- Side Catalog Container -->\n        {% if page.catalog %}\n            <div class=\"\n                col-lg-2 col-lg-offset-0\n                visible-lg-block\n                sidebar-container\n                catalog-container\">\n                <div class=\"side-catalog\">\n                    <hr class=\"hidden-sm hidden-xs\">\n                    <h5>\n                        <a class=\"catalog-toggle\" href=\"#\">CATALOG</a>\n                    </h5>\n                    <ul class=\"catalog-body\"></ul>\n                </div>\n            </div>\n        {% endif %}\n\n    <!-- Sidebar Container -->\n            <div class=\"\n                col-lg-8 col-lg-offset-2\n                col-md-10 col-md-offset-1\n                sidebar-container\">\n\n                <!-- Featured Tags -->\n                {% include featured-tags.html bottom=true %}\n\n                <!-- Friends Blog -->\n                {% include friends.html %}\n            </div>\n        </div>\n    </div>\n</article>\n\n<!-- add support for mathjax by voleking-->\n{% if page.mathjax %}\n  {% include mathjax_support.html %}\n{% endif %}\n\n{% if site.netease_comment %}\n<!-- 网易云跟帖JS代码 start -->\n<script src=\"https://img1.cache.netease.com/f2e/tie/yun/sdk/loader.js\"></script>\n<script>\n  var cloudTieConfig = {\n    url: document.location.href, \n    sourceId: \"\",\n    productKey: \"de25fc98a6fe48b3bc8a7ae765da99a0\",\n    target: \"cloud-tie-wrapper\"\n  };\n  var yunManualLoad = true;\n  Tie.loader(\"aHR0cHM6Ly9hcGkuZ2VudGllLjE2My5jb20vcGMvbGl2ZXNjcmlwdC5odG1s\", true);\n</script>\n<!-- 网易云跟帖JS代码 end -->\n{% endif %}\n\n\n{% if site.disqus_username %}\n<!-- disqus 公共JS代码 start (一个网页只需插入一次) -->\n<script type=\"text/javascript\">\n    /* * * CONFIGURATION VARIABLES * * */\n    var disqus_shortname = \"{{site.disqus_username}}\";\n    var disqus_identifier = \"{{page.id}}\";\n    var disqus_url = \"{{site.url}}{{page.url}}\";\n\n    (function() {\n        var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;\n        dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';\n        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);\n    })();\n</script>\n<!-- disqus 公共JS代码 end -->\n{% endif %}\n\n\n{% if site.anchorjs %}\n<!-- async load function -->\n<script>\n    function async(u, c) {\n      var d = document, t = 'script',\n          o = d.createElement(t),\n          s = d.getElementsByTagName(t)[0];\n      o.src = u;\n      if (c) { o.addEventListener('load', function (e) { c(null, e); }, false); }\n      s.parentNode.insertBefore(o, s);\n    }\n</script>\n<!-- anchor-js, Doc:http://bryanbraun.github.io/anchorjs/ -->\n<script>\n    async(\"//cdnjs.cloudflare.com/ajax/libs/anchor-js/1.1.1/anchor.min.js\",function(){\n        anchors.options = {\n          visible: 'always',\n          placement: 'right',\n          icon: '#'\n        };\n        anchors.add().remove('.intro-header h1').remove('.subheading').remove('.sidebar-container h5');\n    })\n</script>\n<style>\n    /* place left on bigger screen */\n    @media all and (min-width: 800px) {\n        .anchorjs-link{\n            position: absolute;\n            left: -0.75em;\n            font-size: 1.1em;\n            margin-top : -0.1em;\n        }\n    }\n</style>\n{% endif %}\n"
  },
  {
    "path": "_posts/2018-01-01-Dubbo 注解驱动.md",
    "content": "# Dubbo 注解驱动（Annotation-Driven）\n\n\n\n\n## 注解驱动（Annotation-Driven）\n\n\n\n\n\n### `@DubboComponentScan`\n\n\n\n\n\n#### 起始版本： `2.5.7`\n\n\n\n\n\n\n\n#### `<dubbo:annotation> `历史遗留问题\n\n\n\n\n\n##### 1. 注解支持不充分\n\n\n\n在 Dubbo  `2.5.7`之前的版本 ，Dubbo 提供了两个核心注解 `@Service` 以及 `@Reference`，分别用于Dubbo 服务提供和 Dubbo 服务引用。\n\n\n\n其中，`@Service` 作为 XML 元素 `<dubbo:service>`的替代注解，与 Spring Framework `@org.springframework.stereotype.Service` 类似，用于服务提供方 Dubbo 服务暴露。与之相对应的`@Reference`，则是替代`<dubbo:reference` 元素，类似于 Spring 中的 `@Autowired`。\n\n\n\n `2.5.7` 之前的Dubbo，与早期的 Spring Framework 2.5 存在类似的不足，即注解支持不够充分。注解需要和 XML 配置文件配合使用，如下所示：\n\n```xml\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<beans xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n       xmlns:dubbo=\"http://code.alibabatech.com/schema/dubbo\"\n       xmlns=\"http://www.springframework.org/schema/beans\"\n       xsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd\n\thttp://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd\">\n\n    <dubbo:application name=\"annotation-provider\"/>\n    <dubbo:registry address=\"127.0.0.1:4548\"/>\n    <dubbo:annotation package=\"com.alibaba.dubbo.config.spring.annotation.provider\"/>\n\n</beans>\n```\n\n\n\n\n\n##### 2.  `@Service` Bean 不支持 Spring AOP\n\n\n\n同时，使用 `<dubbo:annotation> ` 方式扫描后的Dubbo `@Service` ，在 Spring 代理方面存在问题，如 GitHub 上的 issue https://github.com/alibaba/dubbo/issues/794：\n\n> 关于dubbo @Service注解生成ServiceBean时, interface获取成spring 的代理对象的bug\n>\n> >在项目里， 我使用了\n> >\n> >```java\n> >@Service\n> >@Transactional\n> >@com.alibaba.dubbo.config.annotation.Service\n> >public class SUserJpushServiceImp\n> >```\n> >\n> >的形式， 来暴露服务。但是在发布服务的时候， interface class 是通过\n> >``\n> >serviceConfig.setInterface(bean.getClass().getInterfaces()[0]);\n> >``\n> >的形式获取， 刚好， 我的service都使用了@Transactional注解， 对象被代理了。所以获取到的interface是Spring的代理接口...\n\n\n\n不少热心的小伙伴不仅发现这个历史遗留问题，而且提出了一些修复方案。同时，为了更好地适配 Spring 生命周期以及将 Dubbo 完全向注解驱动编程模型过渡，因此，引入了全新 Dubbo 组件扫描注解 - `@DubboComponentScan`。\n\n\n\n> 注： `<dubbo:annotation> `  Spring AOP 问题将在 `2.5.9` 中修复：https://github.com/alibaba/dubbo/issues/1125\n\n\n\n\n\n##### 3. @Reference 不支持字段继承性\n\n\n\n假设有一个 Spring Bean `AnnotationAction` 直接通过字段`annotationService` 标记 `@Reference` 引用 `AnnotationService` ：\n\n```java\npackage com.alibaba.dubbo.examples.annotation.action;\n\nimport com.alibaba.dubbo.config.annotation.Reference;\nimport com.alibaba.dubbo.examples.annotation.api.AnnotationService;\nimport org.springframework.stereotype.Component;\n\n\n@Component(\"annotationAction\")\npublic class AnnotationAction {\n\n    @Reference\n    private AnnotationService annotationService;\n\n    public String doSayHello(String name) {\n        return annotationService.sayHello(name);\n    }\n\n}\n```\n\n\n\n当`AnnotationAction`  被 XML 元素 `<dubbo:annotation>` 扫描后：\n\n```xml\n<dubbo:annotation package=\"com.alibaba.dubbo.examples.annotation.action\"/>\n```\n\n\n\n字段 `annotationService` 能够引用到 `AnnotationService`，执行 `doSayHello` 方法能够正常返回。\n\n\n\n如果将字段`annotationService`  抽取到`AnnotationAction` 的父类`BaseAction` 后，`AnnotationService` 无法再被引用，改造如下所示：\n\n`AnnotationAction.java`\n\n```java\n@Component(\"annotationAction\")\npublic class AnnotationAction extends BaseAction {\n\n    public String doSayHello(String name) {\n        return getAnnotationService().sayHello(name);\n    }\n\n}\n```\n\n\n\n`BaseAction.java`\n\n```java\npublic abstract class BaseAction {\n\n    @Reference\n    private AnnotationService annotationService;\n\n    protected AnnotationService getAnnotationService() {\n        return annotationService;\n    }\n}\n```\n\n\n\n改造后，再次执行 `doSayHello` 方法，`NullPointerException` 将会被抛出。说明`<dubbo:annotation>` 并不支持`@Reference` 字段继承性。\n\n\n\n了解了历史问题，集合整体愿景，下面介绍`@DubboComponentScan` 的设计原则。\n\n\n\n\n\n\n\n#### 设计原则\n\n\n\n\n\nSpring Framework 3.1 引入了新 Annotation - `@ComponentScan` ， 完全替代了 XML 元素 ` <context:component-scan>` 。同样， `@DubboComponentScan`  作为 Dubbo `2.5.7` 新增的 Annotation，也是XML 元素  `<dubbo:annotation>` 的替代方案。\n\n\n\n在命名上（类名以及属性方法），为了简化使用和关联记忆，Dubbo 组件扫描 Annotation `@DubboComponentScan`，借鉴了 Spring Boot 1.3 引入的 `@ServletComponentScan`。定义如下：\n\n```java\npublic @interface DubboComponentScan {\n\n    /**\n     * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation\n     * declarations e.g.: {@code @DubboComponentScan(\"org.my.pkg\")} instead of\n     * {@code @DubboComponentScan(basePackages=\"org.my.pkg\")}.\n     *\n     * @return the base packages to scan\n     */\n    String[] value() default {};\n\n    /**\n     * Base packages to scan for annotated @Service classes. {@link #value()} is an\n     * alias for (and mutually exclusive with) this attribute.\n     * <p>\n     * Use {@link #basePackageClasses()} for a type-safe alternative to String-based\n     * package names.\n     *\n     * @return the base packages to scan\n     */\n    String[] basePackages() default {};\n\n    /**\n     * Type-safe alternative to {@link #basePackages()} for specifying the packages to\n     * scan for annotated @Service classes. The package of each class specified will be\n     * scanned.\n     *\n     * @return classes from the base packages to scan\n     */\n    Class<?>[] basePackageClasses() default {};\n\n}\n```\n\n\n\n> 注意：`basePackages()` 和 `value()` 均能支持占位符（placeholder）指定的包名\n\n\n\n在职责上，`@DubboComponentScan` 相对于 Spring Boot `@ServletComponentScan` 更为繁重，原因在于处理 Dubbo  `@Service` 类暴露 Dubbo 服务外，还有帮助 Spring  Bean `@Reference`字段或者方法注入 Dubbo 服务代理。 \n\n\n\n 在场景上，Spring Framework `@ComponentScan` 组件扫描逻辑更为复杂。而在 `@DubboComponentScan`  只需关注 `@Service` 和 `@Reference` 处理。\n\n\n\n在功能上， `@DubboComponentScan`  不但需要提供完整 Spring AOP 支持的能力，而且还得具备`@Reference ` 字段可继承性的能力。\n\n\n\n了解基本设计原则后，下面通过完整的示例，简介`@DubboComponentScan` 使用方法以及注意事项。\n\n\n\n\n\n\n\n#### 使用方法\n\n\n\n后续通过服务提供方（`@Serivce`）以及服务消费方（`@Reference`）两部分来介绍`@DubboComponentScan` 使用方法。\n\n\n\n假设，服务提供方和服务消费分均依赖服务接口`DemoService`:\n\n```java\npackage com.alibaba.dubbo.demo;\n\npublic interface DemoService {\n\n    String sayHello(String name);\n\n}\n```\n\n\n\n\n\n##### 服务提供方（`@Serivce`）\n\n\n\n###### 实现 `DemoService`\n\n\n\n服务提供方实现`DemoService`  - `AnnotationDemoService` ，同时标注 Dubbo `@Service` ：\n\n```java\npackage com.alibaba.dubbo.demo.provider;\n\nimport com.alibaba.dubbo.config.annotation.Service;\nimport com.alibaba.dubbo.demo.DemoService;\n\n/**\n * Annotation {@link DemoService} 实现\n *\n * @author <a href=\"mailto:mercyblitz@gmail.com\">Mercy</a>\n */\n@Service\npublic class AnnotationDemoService implements DemoService {\n\n    @Override\n    public String sayHello(String name) {\n        return \"Hello , \" + name;\n    }\n\n}\n```\n\n\n\n\n\n###### 服务提供方 Annotation 配置\n\n\n\n将 `AnnotationDemoService` 暴露成Dubbo 服务，需要依赖 Spring Bean：`AplicationConfig`、`ProtocolConfig` 以及 `RegistryConfig`  。这三个 Spring Bean 过去可通过 XML 文件方式组装 Spring Bean：\n\n```xml\n<beans xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n       xmlns:dubbo=\"http://code.alibabatech.com/schema/dubbo\"\n       xmlns=\"http://www.springframework.org/schema/beans\"\n       xsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd\n    http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd\n    \">\n\n    <!-- 当前应用信息配置 -->\n    <dubbo:application name=\"dubbo-annotation-provider\"/>\n\n    <!-- 连接注册中心配置 -->\n    <dubbo:registry id=\"my-registry\" address=\"N/A\"/>\n\n    <dubbo:protocol name=\"dubbo\" port=\"12345\"/>\n\n</beans>\n```\n\n \n\n以上装配方式不予推荐，推荐使用 Annotation 配置，因此可以换成 Spring `@Configuration` Bean 的形式：\n\n```java\npackage com.alibaba.dubbo.demo.config;\n\nimport com.alibaba.dubbo.config.ApplicationConfig;\nimport com.alibaba.dubbo.config.ProtocolConfig;\nimport com.alibaba.dubbo.config.RegistryConfig;\nimport com.alibaba.dubbo.config.spring.context.annotation.DubboComponentScan;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * 服务提供方配置\n *\n * @author <a href=\"mailto:mercyblitz@gmail.com\">Mercy</a>\n */\n@Configuration\n@DubboComponentScan(\"com.alibaba.dubbo.demo.provider\") // 扫描 Dubbo 组件\npublic class ProviderConfiguration {\n\n    /**\n     * 当前应用配置\n     */\n    @Bean(\"dubbo-annotation-provider\")\n    public ApplicationConfig applicationConfig() {\n        ApplicationConfig applicationConfig = new ApplicationConfig();\n        applicationConfig.setName(\"dubbo-annotation-provider\");\n        return applicationConfig;\n    }\n\n    /**\n     * 当前连接注册中心配置\n     */\n    @Bean(\"my-registry\")\n    public RegistryConfig registryConfig() {\n        RegistryConfig registryConfig = new RegistryConfig();\n        registryConfig.setAddress(\"N/A\");\n        return registryConfig;\n    }\n\n    /**\n     * 当前连接注册中心配置\n     */\n    @Bean(\"dubbo\")\n    public ProtocolConfig protocolConfig() {\n        ProtocolConfig protocolConfig = new ProtocolConfig();\n        protocolConfig.setName(\"dubbo\");\n        protocolConfig.setPort(12345);\n        return protocolConfig;\n    }\n}\n```\n\n\n\n\n\n###### 服务提供方引导类\n\n\n\n```java\npackage com.alibaba.dubbo.demo.bootstrap;\n\nimport com.alibaba.dubbo.demo.DemoService;\nimport com.alibaba.dubbo.demo.config.ProviderConfiguration;\nimport org.springframework.context.annotation.AnnotationConfigApplicationContext;\n\n/**\n * 服务提供方引导类\n *\n * @author <a href=\"mailto:mercyblitz@gmail.com\">Mercy</a>\n */\npublic class ProviderBootstrap {\n\n    public static void main(String[] args) {\n        // 创建 Annotation 配置上下文\n        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();\n        // 注册配置 Bean\n        context.register(ProviderConfiguration.class);\n        // 启动上下文\n        context.refresh();\n        // 获取 DemoService Bean\n        DemoService demoService = context.getBean(DemoService.class);\n        // 执行 sayHello 方法\n        String message = demoService.sayHello(\"World\");\n        // 控制台输出信息\n        System.out.println(message);\n    }\n    \n}\n```\n\n\n\n`ProviderBootstrap` 启动并执行后，控制输出与预期一致：\n\n```\nHello , World\n```\n\n\n\n以上直接结果说明 `@DubboComponentScan(\"com.alibaba.dubbo.demo.provider\")` 扫描后，标注 Dubbo `@Service` 的 `AnnotationDemoService` 被注册成 Spring Bean，可从 Spring ApplicationContext 自由获取。\n\n\n\n\n\n##### 服务消费方（`@Reference`）\n\n\n\n###### 服务 `DemoService`\n\n\n\n```java\npackage com.alibaba.dubbo.demo.consumer;\n\nimport com.alibaba.dubbo.config.annotation.Reference;\nimport com.alibaba.dubbo.demo.DemoService;\n\n/**\n * Annotation 驱动 {@link DemoService} 消费方\n *\n * @author <a href=\"mailto:mercyblitz@gmail.com\">Mercy</a>\n */\npublic class AnnotationDemoServiceConsumer {\n\n    @Reference(url = \"dubbo://127.0.0.1:12345\")\n    private DemoService demoService;\n\n    public String doSayHell(String name) {\n        return demoService.sayHello(name);\n    }\n}\n```\n\n\n\n\n\n###### 服务消费方 Annotation 配置\n\n\n\n与服务提供方配置类似，服务消费方也许 Dubbo 相关配置 Bean - `ConsumerConfiguration`\n\n```java\npackage com.alibaba.dubbo.demo.config;\n\nimport com.alibaba.dubbo.config.ApplicationConfig;\nimport com.alibaba.dubbo.config.RegistryConfig;\nimport com.alibaba.dubbo.config.spring.context.annotation.DubboComponentScan;\nimport com.alibaba.dubbo.demo.consumer.AnnotationDemoServiceConsumer;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * 服务消费方配置\n *\n * @author <a href=\"mailto:mercyblitz@gmail.com\">Mercy</a>\n */\n@Configuration\n@DubboComponentScan\npublic class ConsumerConfiguration {\n\n    /**\n     * 当前应用配置\n     */\n    @Bean\n    public ApplicationConfig applicationConfig() {\n        ApplicationConfig applicationConfig = new ApplicationConfig();\n        applicationConfig.setName(\"dubbo-annotation-consumer\");\n        return applicationConfig;\n    }\n\n    /**\n     * 当前连接注册中心配置\n     */\n    @Bean\n    public RegistryConfig registryConfig() {\n        RegistryConfig registryConfig = new RegistryConfig();\n        registryConfig.setAddress(\"N/A\");\n        return registryConfig;\n    }\n\n    /**\n     * 注册 AnnotationDemoServiceConsumer，@DubboComponentScan 将处理其中 @Reference 字段。\n     * 如果 AnnotationDemoServiceConsumer 非 Spring Bean 的话，\n     * 即使 @DubboComponentScan 指定 package 也不会进行处理，与 Spring @Autowired 同理\n     */\n    @Bean\n    public AnnotationDemoServiceConsumer annotationDemoServiceConsumer() {\n        return new AnnotationDemoServiceConsumer();\n    }\n\n}\n```\n\n\n\n###### 服务消费方引导类\n\n\n\n服务消费方需要先引导服务提供方，下面的实例将会启动两个 Spring 应用上下文，首先引导服务提供方 Spring 应用上下文，同时，需要复用前面Annotation 配置 `ProviderConfiguration`：\n\n```java\n    /**\n     * 启动服务提供方上下文\n     */\n    private static void startProviderContext() {\n        // 创建 Annotation 配置上下文\n        AnnotationConfigApplicationContext providerContext = new AnnotationConfigApplicationContext();\n        // 注册配置 Bean\n        providerContext.register(ProviderConfiguration.class);\n        // 启动服务提供方上下文\n        providerContext.refresh();\n    }\n```\n\n\n\n然后引导服务消费方Spring 应用上下文：\n\n```java\n    /**\n     * 启动并且返回服务消费方上下文\n     *\n     * @return AnnotationConfigApplicationContext\n     */\n    private static ApplicationContext startConsumerContext() {\n        // 创建服务消费方 Annotation 配置上下文\n        AnnotationConfigApplicationContext consumerContext = new AnnotationConfigApplicationContext();\n        // 注册服务消费方配置 Bean\n        consumerContext.register(ConsumerConfiguration.class);\n        // 启动服务消费方上下文\n        consumerContext.refresh();\n        // 返回服务消费方 Annotation 配置上下文\n        return consumerContext;\n    }\n```\n\n\n\n完整的引导类实现：\n\n```java\npackage com.alibaba.dubbo.demo.bootstrap;\n\nimport com.alibaba.dubbo.demo.config.ConsumerConfiguration;\nimport com.alibaba.dubbo.demo.config.ProviderConfiguration;\nimport com.alibaba.dubbo.demo.consumer.AnnotationDemoServiceConsumer;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.context.annotation.AnnotationConfigApplicationContext;\n\n/**\n * 服务消费端引导类\n *\n * @author <a href=\"mailto:mercyblitz@gmail.com\">Mercy</a>\n */\npublic class ConsumerBootstrap {\n\n    public static void main(String[] args) {\n        // 启动服务提供方上下文\n        startProviderContext();\n        // 启动并且返回服务消费方上下文\n        ApplicationContext consumerContext = startConsumerContext();\n        // 获取 AnnotationDemoServiceConsumer Bean\n        AnnotationDemoServiceConsumer consumer = consumerContext.getBean(AnnotationDemoServiceConsumer.class);\n        // 执行 doSayHello 方法\n        String message = consumer.doSayHello(\"World\");\n        // 输出执行结果\n        System.out.println(message);\n    }\n\n    /**\n     * 启动并且返回服务消费方上下文\n     *\n     * @return AnnotationConfigApplicationContext\n     */\n    private static ApplicationContext startConsumerContext() {\n        // 创建服务消费方 Annotation 配置上下文\n        AnnotationConfigApplicationContext consumerContext = new AnnotationConfigApplicationContext();\n        // 注册服务消费方配置 Bean\n        consumerContext.register(ConsumerConfiguration.class);\n        // 启动服务消费方上下文\n        consumerContext.refresh();\n        // 返回服务消费方 Annotation 配置上下文\n        return consumerContext;\n    }\n\n    /**\n     * 启动服务提供方上下文\n     */\n    private static void startProviderContext() {\n        // 创建 Annotation 配置上下文\n        AnnotationConfigApplicationContext providerContext = new AnnotationConfigApplicationContext();\n        // 注册配置 Bean\n        providerContext.register(ProviderConfiguration.class);\n        // 启动服务提供方上下文\n        providerContext.refresh();\n    }\n\n}\n```\n\n\n\n运行`ConsumerBootstrap`结果，仍然符合期望，`AnnotationDemoServiceConsumer` 输出：\n\n```\nHello , World\n```\n\n\n\n\n\n\n\n####  Spring AOP 支持\n\n\n\n前面提到 `<dubbo:annotation> `  注册 Dubbo `@Service` 组件后，在 Spring AOP 支持方面存在问题。事务作为 Spring AOP 的功能扩展，自然也会在 `<dubbo:annotation> `中不支持。\n\n\n\n`@DubboComponentScan` 针对以上问题，实现了对 Spring AOP 是完全兼容。将上述服务提供方 Annotation 配置做出一定的调整，标注`@EnableTransactionManagement` 以及自定义实现`PlatformTransactionManager` :\n\n```java\n@Configuration\n@DubboComponentScan(\"com.alibaba.dubbo.demo.provider\") // 扫描 Dubbo 组件\n@EnableTransactionManagement // 激活事务管理\npublic class ProviderConfiguration {\n  // 省略其他配置 Bean 定义\n  \n    /**\n     * 自定义事务管理器\n     */\n    @Bean\n    @Primary\n    public PlatformTransactionManager transactionManager() {\n        return new PlatformTransactionManager() {\n\n            @Override\n            public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {\n                System.out.println(\"get transaction ...\");\n                return new SimpleTransactionStatus();\n            }\n\n            @Override\n            public void commit(TransactionStatus status) throws TransactionException {\n                System.out.println(\"commit transaction ...\");\n            }\n\n            @Override\n            public void rollback(TransactionStatus status) throws TransactionException {\n                System.out.println(\"rollback transaction ...\");\n            }\n        };\n    }\n}\n```\n\n\n\n同时调整 `AnnotationDemoService`  - 增加`@Transactional` 注解：\n\n```java\n@Service\n@Transactional\npublic class AnnotationDemoService implements DemoService {\n\t// 省略实现，保持不变\n}\n```\n\n\n\n再次运行`ConsumerBootstrap` , 观察控制台输出内容：\n\n```\nget transaction ...\ncommit transaction ...\nHello , World\n```\n\n\n\n输入内容中多处了两行，说明自定义 `PlatformTransactionManager` `getTransaction(TransactionDefinition)` 以及 `commit(TransactionStatus) ` 方法被执行，进而说明 `AnnotationDemoService` 的`sayHello(String)` 方法执行时，事务也伴随执行。\n\n\n\n\n\n#### 注意事项\n\n\n\n`ConsumerConfiguration` 上的  `@DubboComponentScan` 并没有指定 `basePackages` 扫描，这种情况会将`ConsumerConfiguration`  当做 `basePackageClasses` ，即扫描`ConsumerConfiguration` 所属的 package  `com.alibaba.dubbo.demo.config` 以及子 package。由于当前示例中，不存在标注 Dubbo `@Service`的类，因此在运行时日志（如果开启的话）会输出警告信息：\n\n```\nWARN :  [DUBBO] No Spring Bean annotating Dubbo's @Service was found in Spring BeanFactory, dubbo version: 2.0.0, current host: 127.0.0.1\n```\n\n\n\n以上信息大可不必担忧，因为 `@DubboComponentScan`  除了扫描 Dubbo `@Service` 组件以外，还将处理 `@Reference`字段注入。然而读者特别关注`@Reference`字段注入的规则。\n\n\n\n以上实现为例，`AnnotationDemoServiceConsumer` 必须申明为 Spring  `@Bean` 或者 `@Component`（或者其派生注解），否则 `@DubboComponentScan` 不会主动将标注 `@Reference`字段所在的声明类提成为 Spring Bean，换句话说，如果 `@Reference`字段所在的声明类不是 Spring Bean 的话， `@DubboComponentScan` 不会处理`@Reference`注入，其原理与 Spring `@Autowired` 一致。\n\n\n\n以上使用不当可能会导致相关问题，如 GitHub 上曾有小伙伴提问：https://github.com/alibaba/dubbo/issues/825\n\n> **li362692680** 提问：\n>\n> > @DubboComponentScan注解在消费端扫描包时扫描的是 @Service注解？？不是@Reference注解？？\n> > 启动时报\n> > DubboComponentScanRegistrar-85]-[main]-[INFO] 0 annotated @Service Components { [] }\n>\n> 笔者(**mercyblitz**)回复：\n>\n> > `@Reference` 类似于 `@Autowired` 一样，首先其申明的类必须被 Spring 上下文当做一个Bean，因此，Dubbo 并没有直接将 `@Reference`  字段所在的类提升成 Bean。\n> >\n> > 综上所述，这并不是一个问题，而是用法不当！\n\n\n\n\n\n#### 已知问题\n\n\n\n最新发布的 Dubbo `2.5.8` 中，`@DubboComponentScan`  在以下特殊场景下存在 Spring `@Service` 不兼容情况：\n\n> 假设有两个服务实现类 `A` 和 `B`，同时存放在`com.acme` 包下：\n>\n> * `A` 标注  Dubbo `@Service` \n> * `B` 标注  Dubbo `@Service` 和 Spring `@Service`\n>\n> 当 Spring `@ComponentScan` 先扫描`com.acme` 包时，`B` 被当做 Spring Bean 的候选类。随后，`@DubboComponentScan` 也扫描相同的包。当应用启动时，`A` 和 `B`  虽然都是  Spring Bean，可仅 `A` 能够暴露 Dubbo 服务，`B` 则丢失。\n\n\n\n问题版本：`2.5.7`、`2.5.8`\n\n问题详情：https://github.com/alibaba/dubbo/issues/1120\n\n修复版本：`2.5.9`（下个版本）\n\n\n\n\n\n\n\n"
  },
  {
    "path": "_posts/2018-01-18-Dubbo 外部化配置.md",
    "content": "# Dubbo 外部化配置（Externalized Configuration）\n\n\n\n\n## 外部化配置（External Configuration）\n\n\n\n在[Dubbo 注解驱动](Dubbo-Annotation-Driven.md)例子中，无论是服务提供方，还是服务消费方，均需要转配相关配置Bean：\n\n```java\n    @Bean\n    public ApplicationConfig applicationConfig() {\n        ApplicationConfig applicationConfig = new ApplicationConfig();\n        applicationConfig.setName(\"dubbo-annotation-consumer\");\n        return applicationConfig;\n    }\n```\n\n\n\n虽然实现类似于`ProviderConfiguration` 和 `ConsumerConfiguration` 这样的 Spring  `@Configuration` Bean 成本并不高，不过通过 Java Code 的方式定义配置 Bean，或多或少是一种 Hard Code（硬编码）的行为，缺少弹性。\n\n\n\n尽管在 Spring 应用中，可以通过 `@Value` 或者 `Environment` 的方式获取外部配置，其代码简洁性以及类型转换灵活性存在明显的不足。因此，Spring Boot  提出了外部化配置（External Configuration）的感念，即通过程序以外的配置源，动态地绑定指定类型。\n\n\n\n随着 Spring Boot / Spring Cloud 应用的流行，开发人员逐渐地接受并且使用 Spring Boot 外部化配置（External Configuration），即通过 `application.properties` 或者 `bootstrap.properties` 装配配置 Bean。\n\n\n\n下列表格记录了 Dubbo 内置配置类：\n\n| 配置类                 | 标签                     | 用途     | 解释                                       |\n| ------------------- | ---------------------- | ------ | ---------------------------------------- |\n| `ProtocolConfig`    | `<dubbo:protocol/>`    | 协议配置   | 用于配置提供服务的协议信息，协议由提供方指定，消费方被动接受           |\n| `ApplicationConfig` | `<dubbo:application/>` | 应用配置   | 用于配置当前应用信息，不管该应用是提供者还是消费者                |\n| `ModuleConfig`      | `<dubbo:module/>`      | 模块配置   | 用于配置当前模块信息，可选                            |\n| `RegistryConfig`    | `<dubbo:registry/>`    | 注册中心配置 | 用于配置连接注册中心相关信息                           |\n| `MonitorConfig`     | `<dubbo:monitor/>`     | 监控中心配置 | 用于配置连接监控中心相关信息，可选                        |\n| `ProviderConfig`    | `<dubbo:provider/>`    | 提供方配置  | 当 ProtocolConfig 和 ServiceConfig 某属性没有配置时，采用此缺省值，可选 |\n| `ConsumerConfig`    | `<dubbo:consumer/>`    | 消费方配置  | 当 ReferenceConfig 某属性没有配置时，采用此缺省值，可选     |\n| `MethodConfig`      | `<dubbo:method/>`      | 方法配置   | 用于 ServiceConfig 和 ReferenceConfig 指定方法级的配置信息 |\n| `ArgumentConfig`    | `<dubbo:argument/>`    | 参数配置   | 用于指定方法参数配置                               |\n\n\n\n通过申明对应的 Spring 扩展标签，在 Spring 应用上下文中将自动生成相应的配置 Bean。\n\n\n\n在 Dubbo 官方用户手册的[“属性配置”](http://dubbo.io/books/dubbo-user-book/configuration/properties.html)章节中，`dubbo.properties` 配置属性能够映射到  `ApplicationConfig` 、`ProtocolConfig` 以及 `RegistryConfig` 的字段。从某种意义上来说，`dubbo.properties`  也是 Dubbo 的外部化配置。\n\n\n\n其中，引用“映射规则”的内容：\n\n>## 映射规则\n>\n>将 XML 配置的标签名，加属性名，用点分隔，多个属性拆成多行\n>\n>* 比如：`dubbo.application.name=foo`等价于`<dubbo:application name=\"foo\" />`\n>* 比如：`dubbo.registry.address=10.20.153.10:9090`等价于`<dubbo:registryaddress=\"10.20.153.10:9090\" />`\n>\n>如果 XML 有多行同名标签配置，可用 id 号区分，如果没有 id 号将对所有同名标签生效\n>\n>* 比如：`dubbo.protocol.rmi.port=1234`等价于`<dubbo:protocol id=\"rmi\" name=\"rmi\" port=\"1099\" />`[2](http://dubbo.io/books/dubbo-user-book/configuration/properties.html#fn_2)\n>* 比如：`dubbo.registry.china.address=10.20.153.10:9090`等价于`<dubbo:registry id=\"china\"address=\"10.20.153.10:9090\" />`\n>\n>下面是 dubbo.properties 的一个典型配置：\n>\n>```\n>dubbo.application.name=foo\n>dubbo.application.owner=bar\n>dubbo.registry.address=10.20.153.10:9090\n>```\n\n\n\n根据“映射规则”，Dubbo 即支持单配置 Bean 映射，也支持多 Bean 映射。综合以上需求，既要兼容 Dubbo 已有的一个或多个 Bean 字段映射绑定，也支持外部化配置。\n\n> 特别提醒：外部化配置（External Configuration）并非 Spring Boot 特有，即使在 Spring Framework 场景下亦能支持。也就是说 Dubbo 外部化配置即可在 Spring Framework 中工作，也能在 Spring Boot 中运行。\n\n\n\nDubbo 外部化配置（External Configuration） 支持起始版本为：`2.5.8`\n\n\n\n\n\n### `@EnableDubboConfig`\n\n\n\n#### 起始版本：`2.5.8`\n\n\n\n#### 使用说明\n\n\n\n##### `@EnableDubboConfig` 定义\n\n```java\npublic @interface EnableDubboConfig {\n\n    /**\n     * It indicates whether binding to multiple Spring Beans.\n     *\n     * @return the default value is <code>false</code>\n     * @revised 2.5.9\n     */\n    boolean multiple() default false;\n\n}\n```\n\n\n\n* `multiple` : 表示是否支持多Dubbo 配置 Bean 绑定。默认值为 `false` ，即单 Dubbo 配置 Bean 绑定\n\n\n\n##### 单 Dubbo 配置 Bean 绑定\n\n\n\n为了更好地向下兼容，`@EnableDubboConfig` 提供外部化配置属性与 Dubbo 配置类之间的绑定，其中映射关系如下：\n\n| 配置类                 | 外部化配置属性前缀           | 用途     |\n| ------------------- | ------------------- | ------ |\n| `ProtocolConfig`    | `dubbo.protocol`    | 协议配置   |\n| `ApplicationConfig` | `dubbo.application` | 应用配置   |\n| `ModuleConfig`      | `dubbo.module`      | 模块配置   |\n| `RegistryConfig`    | `dubbo.registry`    | 注册中心配置 |\n| `MonitorConfig`     | `dubbo.monitor`     | 监控中心配置 |\n| `ProviderConfig`    | `dubbo.provider`    | 提供方配置  |\n| `ConsumerConfig`    | `dubbo.consumer`    | 消费方配置  |\n\n\n\n当标注 `@EnableDubboConfig` 的类被扫描注册后，同时  Spring（Spring Boot）应用配置（`PropertySources`）中存在`dubbo.application.*` 时，`ApplicationConfig`  Bean 将被注册到在 Spring 上下文。否则，不会被注册。如果出现`dubbo.registry.*`的配置，那么，`RegistryConfig` Bean 将会创建，以此类推。即按需装配 Dubbo 配置 Bean。\n\n\n\n如果需要指定配置 Bean的 id，可通过`**.id` 属性设置，以`dubbo.application` 为例：\n\n```properties\n## application\ndubbo.application.id = applicationBean\ndubbo.application.name = dubbo-demo-application\n```\n\n\n\n以上配置等同于以下 Java Config Bean：\n\n```java\n    @Bean(\"applicationBean\")\n    public ApplicationConfig applicationBean() {\n        ApplicationConfig applicationConfig = new ApplicationConfig();\n        applicationConfig.setName(\"dubbo-demo-application\");\n        return applicationConfig;\n    }\n```\n\n\n\n大致上配置属性与配置类绑定模式 - `dubbo.application.* ` 映射到 `ApplicationConfig` 中的字段。\n\n> 注：当配置属性名称无法在配置类中找到字段时，将会忽略绑定\n\n\n\n\n\n##### 多 Dubbo 配置 Bean 绑定\n\n\n\nDubbo `@Service` 和 `@Reference` 允许 Dubbo 应用关联`ApplicationConfig` Bean 或者指定多个`RegistryConfig` Bean 等能力。换句话说，Dubbo 应用上下文中可能存在多个`ApplicationConfig` 等 Bean定义。\n\n\n\n为了适应以上需要，因此从Dubbo `2.5.9` 开始，`@EnableDubboConfig` 支持多 Dubbo 配置 Bean 绑定，同时按照业界规约标准，与单 Dubbo 配置 Bean 绑定约定不同，配置属性前缀均为英文复数形式：\n\n> 详情请参考 ：https://github.com/alibaba/dubbo/issues/1141\n\n\n\n* `dubbo.applications`\n* `dubbo.modules`\n* `dubbo.registries`\n* `dubbo.protocols`\n* `dubbo.monitors`\n* `dubbo.providers`\n* `dubbo.consumers`\n\n\n\n以`dubbo.applications` 为例，基本的模式如下：\n\n```properties\ndubbo.applications.${bean-name}.property-name = ${property-value}\n```\n\n\n\n请读者注意，在单 Dubbo 配置 Bean 绑定时，可以通过指定`id` 属性的方式，定义`ApplicationConfig` Bean 的ID，即`dubbo.application.id`。\n\n而在多 Dubbo 配置 Bean 绑定时，Bean ID 则由`dubbo.applications.`与属性字段名称（`.property-name`)之间的字符来表达。\n\n\n\n如下配置：\n\n```properties\n# multiple Bean definition\ndubbo.applications.applicationBean.name = dubbo-demo-application\ndubbo.applications.applicationBean2.name = dubbo-demo-application2\ndubbo.applications.applicationBean3.name = dubbo-demo-application3\n```\n\n\n\n该配置内容中，绑定了三个`ApplicationConfig` Bean，分别是`applicationBean`、`applicationBean2`以及`applicationBean3`\n\n\n\n#### 示例说明\n\n\n\n`@EnableDubboConfig` 的使用方法很简答， 再次强调一点，当规约的外部配置存在时，相应的 Dubbo 配置类 才会提升为 Spring Bean。简言之，按需装配。\n\n\n\n\n\n##### 单 Dubbo 配置 Bean 绑定\n\n\n\n###### 外部化配置文件\n\n\n\n将以下内容的外部化配置文件物理路径为：`classpath:/META-INF/config.properties`:\n\n```properties\n# 单 Dubbo 配置 Bean 绑定\n## application\ndubbo.application.id = applicationBean\ndubbo.application.name = dubbo-demo-application\n\n## module\ndubbo.module.id = moduleBean\ndubbo.module.name = dubbo-demo-module\n\n## registry\ndubbo.registry.address = zookeeper://192.168.99.100:32770\n\n## protocol\ndubbo.protocol.name = dubbo\ndubbo.protocol.port = 20880\n\n## monitor\ndubbo.monitor.address = zookeeper://127.0.0.1:32770\n\n## provider\ndubbo.provider.host = 127.0.0.1\n\n## consumer\ndubbo.consumer.client = netty\n```\n\n\n\n###### `@EnableDubboConfig` 配置 Bean\n\n\n\n```java\n/**\n * Dubbo 配置 Bean\n *\n * @author <a href=\"mailto:mercyblitz@gmail.com\">Mercy</a>\n */\n@EnableDubboConfig\n@PropertySource(\"META-INF/config.properties\")\n@Configuration\npublic class DubboConfiguration {\n\n}\n```\n\n\n\n###### 实现引导类\n\n\n\n```java\n/**\n * Dubbo 配置引导类\n *\n * @author <a href=\"mailto:mercyblitz@gmail.com\">Mercy</a>\n */\npublic class DubboConfigurationBootstrap {\n\n    public static void main(String[] args) {\n        // 创建配置上下文\n        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();\n        // 注册当前配置 Bean\n        context.register(DubboConfiguration.class);\n        context.refresh();\n \t    // application\n        ApplicationConfig applicationConfig = context.getBean(\"applicationBean\", ApplicationConfig.class);\n        System.out.printf(\"applicationBean.name = %s \\n\", applicationConfig.getName());\n\n        // module\n        ModuleConfig moduleConfig = context.getBean(\"moduleBean\", ModuleConfig.class);\n        System.out.printf(\"moduleBean.name = %s \\n\", moduleConfig.getName());\n\n        // registry\n        RegistryConfig registryConfig = context.getBean(RegistryConfig.class);\n        System.out.printf(\"registryConfig.name = %s \\n\", registryConfig.getAddress());\n\n        // protocol\n        ProtocolConfig protocolConfig = context.getBean(ProtocolConfig.class);\n        System.out.printf(\"protocolConfig.name = %s \\n\", protocolConfig.getName());\n        System.out.printf(\"protocolConfig.port = %s \\n\", protocolConfig.getPort());\n\n        // monitor\n        MonitorConfig monitorConfig = context.getBean(MonitorConfig.class);\n        System.out.printf(\"monitorConfig.name = %s \\n\", monitorConfig.getAddress());\n\n        // provider\n        ProviderConfig providerConfig = context.getBean(ProviderConfig.class);\n        System.out.printf(\"providerConfig.name = %s \\n\", providerConfig.getHost());\n\n        // consumer\n        ConsumerConfig consumerConfig = context.getBean(ConsumerConfig.class);\n        System.out.printf(\"consumerConfig.name = %s \\n\", consumerConfig.getClient());\n    }\n}\n```\n\n\n\n###### 执行结果\n\n\n\n```\napplicationBean.name = dubbo-demo-application \nmoduleBean.name = dubbo-demo-module \nregistryConfig.name = zookeeper://192.168.99.100:32770 \nprotocolConfig.name = dubbo \nprotocolConfig.port = 20880 \nmonitorConfig.name = zookeeper://127.0.0.1:32770 \nproviderConfig.name = 127.0.0.1 \nconsumerConfig.name = netty \n```\n\n\n\n不难发现，`@EnableDubboConfig` 配置 Bean 配合外部化文件 `classpath:/META-INF/config.properties`，与执行输出内容相同。\n\n\n\n\n\n##### 多 Dubbo 配置 Bean 绑定\n\n\n\n###### 外部化配置文件\n\n\n\n将以下内容的外部化配置文件物理路径为：`classpath:/META-INF/multiple-config.properties`:\n\n```properties\n# 多 Dubbo 配置 Bean 绑定\n## dubbo.applications\ndubbo.applications.applicationBean.name = dubbo-demo-application\ndubbo.applications.applicationBean2.name = dubbo-demo-application2\ndubbo.applications.applicationBean3.name = dubbo-demo-application3\n```\n\n\n\n######  `@EnableDubboConfig`  配置 Bean（多）\n\n\n\n```java\n@EnableDubboConfig(multiple = true)\n@PropertySource(\"META-INF/multiple-config.properties\")\nprivate static class DubboMultipleConfiguration {\n\n}\t\n```\n\n\n\n###### 实现引导类\n\n\n\n```java\n/**\n * Dubbo 配置引导类\n *\n * @author <a href=\"mailto:mercyblitz@gmail.com\">Mercy</a>\n */\npublic class DubboConfigurationBootstrap {\n    public static void main(String[] args) {\n        // 创建配置上下文\n        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();\n        // 注册当前配置 Bean\n        context.register(DubboMultipleConfiguration.class);\n        context.refresh();\n\n        // 获取 ApplicationConfig Bean：\"applicationBean\"、\"applicationBean2\" 和 \"applicationBean3\"\n        ApplicationConfig applicationBean = context.getBean(\"applicationBean\", ApplicationConfig.class);\n        ApplicationConfig applicationBean2 = context.getBean(\"applicationBean2\", ApplicationConfig.class);\n        ApplicationConfig applicationBean3 = context.getBean(\"applicationBean3\", ApplicationConfig.class);\n\n        System.out.printf(\"applicationBean.name = %s \\n\", applicationBean.getName());\n        System.out.printf(\"applicationBean2.name = %s \\n\", applicationBean2.getName());\n        System.out.printf(\"applicationBean3.name = %s \\n\", applicationBean3.getName());\n    }\n}\n```\n\n\n\n###### 执行结果\n\n\n\n```\napplicationBean.name = dubbo-demo-application \napplicationBean2.name = dubbo-demo-application2 \napplicationBean3.name = dubbo-demo-application3 \n```\n\n\n\n`@EnableDubboConfig(multiple = true)` 执行后，运行结果说明`ApplicationConfig` Bean 以及 ID 的定义方式。 \n\n\n\n\n\n### `@EnableDubboConfigBinding` & `@EnableDubboConfigBindings`\n\n\n\n`@EnableDubboConfig`适合绝大多数外部化配置场景，然而无论是单 Bean 绑定，还是多 Bean 绑定，其**外部化配置属性前缀**是固化的，如`dubbo.application` 以及 `dubbo.applications` 。\n\n\n\n当应用需要自定义**外部化配置属性前缀**，`@EnableDubboConfigBinding`能提供更大的弹性，支持单个外部化配置属性前缀（`prefix`) 与 Dubbo 配置 Bean 类型（`AbstractConfig` 子类）绑定，如果需要多次绑定时，可使用`@EnableDubboConfigBindings`。\n\n> 尽管 Dubbo 推荐使用 Java 8 ，然而实际的情况，运行时的 JDK 的版本可能从 6到8 均有。因此，`@EnableDubboConfigBinding` 没有实现`java.lang.annotation.Repeatable`，即允许实现类不支持重复标注`@EnableDubboConfigBinding`。\n\n\n\n`@EnableDubboConfigBinding`  在支持外部化配置属性与 Dubbo 配置类绑定时，与 Dubbo 过去的映射行为不同，被绑定的 Dubbo 配置类将会提升为 Spring Bean，无需提前装配 Dubbo 配置类。同时，支持多 Dubbo 配置Bean 装配。其 Bean 的绑定规则与`@EnableDubboConfig`一致。\n\n\n\n#### 起始版本： `2.5.8`\n\n\n\n#### 使用说明\n\n\n\n##### `@EnableDubboConfigBinding` 定义\n\n\n\n```java\npublic @interface EnableDubboConfigBinding {\n\n    /**\n     * The name prefix of the properties that are valid to bind to {@link AbstractConfig Dubbo Config}.\n     *\n     * @return the name prefix of the properties to bind\n     */\n    String prefix();\n\n    /**\n     * @return The binding type of {@link AbstractConfig Dubbo Config}.\n     * @see AbstractConfig\n     * @see ApplicationConfig\n     * @see ModuleConfig\n     * @see RegistryConfig\n     */\n    Class<? extends AbstractConfig> type();\n\n    /**\n     * It indicates whether {@link #prefix()} binding to multiple Spring Beans.\n     *\n     * @return the default value is <code>false</code>\n     */\n    boolean multiple() default false;\n\n}\n```\n\n\n\n* `prefix()` : 指定待绑定 Dubbo 配置类的外部化配置属性的前缀，比如`dubbo.application`  为 `ApplicationConfig` 的外部化配置属性的前缀。`prefix()` 支持占位符（Placeholder）, 并且其关联前缀值是否以\".\" 作为结尾字符是可选的，即`prefix() = \"dubbo.application\"` 与 `prefix() = \"dubbo.application.\"` 效果相同 \n* `type()` : 指定 Dubbo 配置类，所有 `AbstractConfig` 的实现子类即可，如`ApplicationConfig` 、`RegistryConfig` 以及 `ProtocolConfig` 等\n* `multiple()` : 表明是否需要将`prefix()`   作为多个 `type()`   类型的 Spring Bean 外部化配置属性。默认值为`false`，即默认支持单个类型的 Spring 配置 Bean\n\n\n\n\n假设标注 `@EnableDubboConfigBinding` 的实现类被 Spring 应用上下文扫描并且注册后，其中`prefix()` =  `dubbo.app` 、 `type()` = `ApplicationConfig.class` ，且外部配置内容为：\n\n```properties\ndubbo.app.id = applicationBean\ndubbo.app.name = dubbo-demo-application\n```\n\n\n\nSpring 应用上下文启动后，一个 ID 为 \"applicationBean\"   的 `ApplicationConfig`  Bean 被初始化，其 `name` 字段被设置为 \"dubbo-demo-application\"。\n\n\n\n##### `EnableDubboConfigBindings` 定义\n\n\n\n```java\npublic @interface EnableDubboConfigBindings {\n\n    /**\n     * The value of {@link EnableDubboConfigBindings}\n     *\n     * @return non-null\n     */\n    EnableDubboConfigBinding[] value();\n\n}\n```\n\n\n\n* `value` : 指定多个`EnableDubboConfigBinding`，用于实现外部化配置属性前缀（`prefix`) 与 Dubbo 配置 Bean 类型（`AbstractConfig` 子类）绑定。\n\n\n\n#### 示例说明\n\n\n\n##### 外部化配置文件\n\n\n\n将以下内容的外部化配置文件物理路径为：`classpath:/META-INF/bindings.properties`\n\n```properties\n# classpath:/META-INF/bindings.properties\n## 占位符值 : ApplicationConfig 外部配置属性前缀\napplications.prefix = dubbo.apps.\n\n## 多 ApplicationConfig Bean 绑定\ndubbo.apps.applicationBean.name = dubbo-demo-application\ndubbo.apps.applicationBean2.name = dubbo-demo-application2\ndubbo.apps.applicationBean3.name = dubbo-demo-application3\n\n## 单 ModuleConfig Bean 绑定\ndubbo.module.id = moduleBean\ndubbo.module.name = dubbo-demo-module\n\n## 单 RegistryConfig Bean 绑定\ndubbo.registry.address = zookeeper://192.168.99.100:32770\n```\n\n\n\n\n\n##### `EnableDubboConfigBindings` 配置 Bean\n\n\n\n`DubboConfiguration` 作为 Dubbo 配置 Bean，除通过 `@EnableDubboConfigBinding` 绑定之外，还需要 `@PropertySource` 指定外部化配置文件（`classpath:/META-INF/bindings.properties`）:\n\n```java\n/**\n * Dubbo 配置 Bean\n *\n * @author <a href=\"mailto:mercyblitz@gmail.com\">Mercy</a>\n */\n@EnableDubboConfigBindings({\n        @EnableDubboConfigBinding(prefix = \"${applications.prefix}\",\n                type = ApplicationConfig.class, multiple = true), // 多 ApplicationConfig Bean 绑定\n        @EnableDubboConfigBinding(prefix = \"dubbo.module\", // 不带 \".\" 后缀\n                type = ModuleConfig.class), // 单 ModuleConfig Bean 绑定\n        @EnableDubboConfigBinding(prefix = \"dubbo.registry.\", // 带 \".\" 后缀\n                type = RegistryConfig.class) // 单 RegistryConfig Bean 绑定\n})\n@PropertySource(\"META-INF/bindings.properties\")\n@Configuration\npublic class DubboConfiguration {\n  \n}\n```\n\n\n\n\n\n##### 实现引导类\n\n\n\n通过之前的使用说明，当 `EnableDubboConfigBinding` 将外部配置化文件`classpath:/META-INF/dubbo.properties` 绑定到 `ApplicationConfig`后，其中 Spring Bean \"applicationBean\" 的 name 字段被设置成 \"dubbo-demo-application\"。同时， `EnableDubboConfigBinding`  所标注的 `DubboConfiguration` 需要被 Sring 应用上下文注册：\n\n```java\n/**\n * Dubbo 配置引导类\n *\n * @author <a href=\"mailto:mercyblitz@gmail.com\">Mercy</a>\n */\npublic class DubboConfigurationBootstrap {\n\n    public static void main(String[] args) {\n        // 创建配置上下文\n        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();\n        // 注册当前配置 Bean\n        context.register(DubboConfiguration.class);\n        context.refresh();\n \t\t// 获取 ApplicationConfig Bean：\"applicationBean\"、\"applicationBean2\" 和 \"applicationBean3\"\n        ApplicationConfig applicationBean = context.getBean(\"applicationBean\", ApplicationConfig.class);\n        ApplicationConfig applicationBean2 = context.getBean(\"applicationBean2\", ApplicationConfig.class);\n        ApplicationConfig applicationBean3 = context.getBean(\"applicationBean3\", ApplicationConfig.class);\n\n        System.out.printf(\"applicationBean.name = %s \\n\", applicationBean.getName());\n        System.out.printf(\"applicationBean2.name = %s \\n\", applicationBean2.getName());\n        System.out.printf(\"applicationBean3.name = %s \\n\", applicationBean3.getName());\n\n        // 获取 ModuleConfig Bean：\"moduleBean\"\n        ModuleConfig moduleBean = context.getBean(\"moduleBean\", ModuleConfig.class);\n\n        System.out.printf(\"moduleBean.name = %s \\n\", moduleBean.getName());\n\n        // 获取 RegistryConfig Bean\n        RegistryConfig registry = context.getBean(RegistryConfig.class);\n\n        System.out.printf(\"registry.address = %s \\n\", registry.getAddress());\n    }\n}\n```\n\n\n\n##### 运行结果\n\n\n\n`DubboConfigurationBootstrap` 运行后控制台输出：\n\n```\napplicationBean.name = dubbo-demo-application \napplicationBean2.name = dubbo-demo-application2 \napplicationBean3.name = dubbo-demo-application3 \nmoduleBean.name = dubbo-demo-module \nregistry.address = zookeeper://192.168.99.100:32770 \n```\n\n\n\n输出的内容与`classpath:/META-INF/bindings.properties` 绑定的内容一致，符合期望。\n"
  },
  {
    "path": "_posts/2018-05-26-Spring-Boot Web 应用加速.md",
    "content": "# Spring Boot Web 应用加速\n\n\n\n默认情况下，Spring Boot Web 应用会装配一些功能组件 Bean。\n\n\n在大多数 Web 应用场景下，可以选择性地关闭一下自动装配的Spring 组件 Bean，以达到提升性能的目的。\n\n\n## 配置项优化\n\n### Spring Boot Web 应用加速 完整配置项\n\n````properties\nmanagement.add-application-context-header = false\nspring.mvc.formcontent.putfilter.enabled = false\n\nspring.autoconfigure.exclude = org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\\\norg.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\\\norg.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\\\norg.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\\\norg.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\\\norg.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\\\norg.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\\\norg.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\\\norg.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\\\norg.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\\\norg.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\\\norg.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\\\norg.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\\\norg.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\\\norg.springframework.boot.actuate.autoconfigure.TraceRepositoryAutoConfiguration,\\\norg.springframework.boot.actuate.autoconfigure.TraceWebFilterAutoConfiguration,\\\norg.springframework.boot.actuate.autoconfigure.MetricFilterAutoConfiguration\n````\n\n### 配置项汇总\n\n````properties\nspring.autoconfigure.exclude = org.springframework.boot.actuate.autoconfigure.TraceRepositoryAutoConfiguration,\\\norg.springframework.boot.actuate.autoconfigure.TraceWebFilterAutoConfiguration,\\\norg.springframework.boot.actuate.autoconfigure.MetricFilterAutoConfiguration\n````\n\n### 关闭 Web 请求跟踪 自动装配\n\n\n#### `org.springframework.boot.actuate.autoconfigure.TraceWebFilterAutoConfiguration`\n\n\n顾名思义，该自动装配用跟踪 Web 请求，通过Servlet Filter `org.springframework.boot.actuate.trace.WebRequestTraceFilter` 记录请求的信息（如：请求方法、请求头以及请求路径等），其计算的过程存在一定的开销，使用场景罕见，故可选择关闭。\n\n\n* 配置项\n\n\n````properties\nspring.autoconfigure.exclude = org.springframework.boot.actuate.autoconfigure.TraceWebFilterAutoConfiguration\n````\n\n\n#### `org.springframework.boot.actuate.autoconfigure.TraceRepositoryAutoConfiguration`\n\n当`org.springframework.boot.actuate.autoconfigure.TraceWebFilterAutoConfiguration`关闭后，其请求信息存储介质`org.springframework.boot.actuate.trace.TraceRepository`没有存在的必要，故可选择关闭。\n\n\n* 配置项\n\n\n````properties\nspring.autoconfigure.exclude = org.springframework.boot.actuate.autoconfigure.TraceRepositoryAutoConfiguration\n````\n\n\n### 关闭 Web 请求结果指标 自动装配\n\n\n#### `org.springframework.boot.actuate.autoconfigure.MetricFilterAutoConfiguration`\n\n\n该组件将自动装配`org.springframework.boot.actuate.autoconfigure.MetricsFilter`，该 Filter\n\n主要记录Web 请求结果指标（如：相应状态码、请求方法执行时间等），该信息一定程度上与反向代理服务器（nginx）功能重叠，故可选择关闭。\n\n\n* 配置项\n\n\n````properties\nspring.autoconfigure.exclude = org.springframework.boot.actuate.autoconfigure.MetricFilterAutoConfiguration\n````\n\n\n\n\n### 可关闭 Servlet Web 组件\n\n#### `org.springframework.web.filter.HttpPutFormContentFilter`\n\n\n* 引入版本\n\n`org.springframework.web.filter.HttpPutFormContentFilter` 由 Spring\nFramework 3.1 版本引入，分发在 `org.springframework:spring-web` 中。\n\n\n* 使用场景\n\n通常 Web 场景中，浏览器通过 HTTP `GET` 或者 `POST` 请求 提交 Form 数据，而非浏览\n器客户端（如应用程序）可能通过 HTTP `PUT` 请求来实现。\n\n当 HTTP 请求头`Content-Type` 为 `application/x-www-form-urlencoded` 时\n，Form 数据被 encoded。而 Servlet 规范中， `ServletRequest.getParameter*()`\n方法仅对 HTTP `POST` 方法支持请求参数的获取，如：\n\n````java\npublic intetfacce ServletRequest {\n\n    ......\n\n    public String getParameter(String name);\n\n    public Enumeration<String> getParameterNames();\n\n    public String[] getParameterValues(String name);\n\n    public Map<String, String[]> getParameterMap();\n\n    ......\n\n}\n````\n\n故 以上方法无法支持 HTTP `PUT` 或 HTTP `PATCH` 请求方法（请求头`Content-Type`\n为`application/x-www-form-urlencoded`）。\n\n`org.springframework.web.filter.HttpPutFormContentFilter` 正是这种场景的解\n决方案。\n\nSpring Boot 默认场景下，将\n`org.springframework.web.filter.HttpPutFormContentFilter` 被\n`org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration` 自动\n装配，以下为 Spring Boot 1.4.1.RELEASE 以及更好版本定义（可能存在一定的差异）：\n\n````java\n@Configuration\n@ConditionalOnWebApplication\n@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,\n\t\tWebMvcConfigurerAdapter.class })\n@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)\n@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)\n@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,\n\t\tValidationAutoConfiguration.class })\npublic class WebMvcAutoConfiguration {\n\n    ......\n\n    @Bean\n    @ConditionalOnMissingBean(HttpPutFormContentFilter.class)\n    @ConditionalOnProperty(prefix = \"spring.mvc.formcontent.putfilter\", name = \"enabled\", matchIfMissing = true)\n    public OrderedHttpPutFormContentFilter httpPutFormContentFilter() {\n        return new OrderedHttpPutFormContentFilter();\n    }\n\n    ......\n\n}\n````\n\n综上所述，`org.springframework.web.filter.HttpPutFormContentFilter` 在绝大\n多数 Web 使用场景下为非必须组件。\n\n\n* 配置项\n\n如果应用依赖 Spring Boot 版本 为 1.4.1.RELEASE 以及更高的版本，可通过如下配置，\n进行将 `org.springframework.web.filter.HttpPutFormContentFilter` 关闭：\n\n````properties\nspring.mvc.formcontent.putfilter.enabled = false\n````\n\n\n#### `org.springframework.web.filter.HiddenHttpMethodFilter`\n\n\n* 引入版本\n\n`org.springframework.web.filter.HiddenHttpMethodFilter` 由 Spring\nFramework 3.0 版本引入，分发在 `org.springframework:spring-web` 中。\n\n\n* 使用场景\n\n当 Web 服务端同一资源（URL）提供了多请求方法的实现，例如 URI ：/update 提供了\nHTTP `POST` 以及 HTTP `PUT` 实现），通常 Web 场景中，浏览器仅支持 HTTP `GET`\n或者 `POST` 请求方法，这样的话，浏览器无法发起 HTTP `PUT` 请求。\n\n为了浏览器可以消费 HTTP `PUT` 资源， 需要在服务端将 HTTP `POST` 转化成\nHTTP `PUT` 请求，为了解决这类问题，Spring 引入\n`org.springframework.web.filter.HiddenHttpMethodFilter` Web 组件。\n\n当浏览器 发起 HTTP `POST` 请求时，可通过增加请求参数（默认参数名称：\"_method\"）\n的方式，进行HTTP 请求方法切换，\n`org.springframework.web.filter.HiddenHttpMethodFilter` 获取参数\"_method\"\n值后，将参数值作为 `HttpServletRequest#getMethod()`的返回值，给后续 `Servlet`\n实现使用。\n\n出于通用性的考虑，`org.springframework.web.filter.HiddenHttpMethodFilter`\n通过调用 `#setMethodParam(String)` 方法，来修改转换请求方法的参数名称。\n\n\nSpring Boot 默认场景下，将\n`org.springframework.web.filter.HttpPutFormContentFilter` 被\n`org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration` 自动\n装配，以下为 Spring Boot 1.4.1.RELEASE 以及更好版本定义（可能存在一定的差异）：\n\n````java\n@Configuration\n@ConditionalOnWebApplication\n@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,\n\t\tWebMvcConfigurerAdapter.class })\n@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)\n@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)\n@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,\n\t\tValidationAutoConfiguration.class })\npublic class WebMvcAutoConfiguration {\n\n    ......\n\n    @Bean\n    @ConditionalOnMissingBean(HiddenHttpMethodFilter.class)\n    public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {\n        return new OrderedHiddenHttpMethodFilter();\n    }\n\n    ......\n\n}\n````\n\n综上所述，`org.springframework.web.filter.HiddenHttpMethodFilter` 也是特殊\n场景下所需，故可以关闭之。\n\n\n* 配置项\n\n按目前最新的 Spring Boot 1.5.2.RELEASE 版本中实现，也没有提供类似\n`spring.mvc.formcontent.putfilter.enabled` 这样的配置项关闭，无法关闭。\n\n"
  },
  {
    "path": "_posts/2018-06-28-Dubbo Cloud Native 实践与思考.md",
    "content": "# Dubbo Cloud Native 实践与思考\n\n<!-- TOC -->\n\n- [Dubbo Cloud Native 实践与思考](#dubbo-cloud-native-%E5%AE%9E%E8%B7%B5%E4%B8%8E%E6%80%9D%E8%80%83)\n    - [分享简介](#%E5%88%86%E4%BA%AB%E7%AE%80%E4%BB%8B)\n    - [自我介绍](#%E8%87%AA%E6%88%91%E4%BB%8B%E7%BB%8D)\n    - [主要议程](#%E4%B8%BB%E8%A6%81%E8%AE%AE%E7%A8%8B)\n        - [Cloud Native 基础设施](#cloud-native-%E5%9F%BA%E7%A1%80%E8%AE%BE%E6%96%BD)\n            - [服务发现（Service Discovery ）](#%E6%9C%8D%E5%8A%A1%E5%8F%91%E7%8E%B0service-discovery)\n                - [如何选择](#%E5%A6%82%E4%BD%95%E9%80%89%E6%8B%A9)\n                    - [Eureka](#eureka)\n                    - [Consul](#consul)\n                    - [Zookeeper](#zookeeper)\n            - [负载均衡](#%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1)\n            - [服务网关](#%E6%9C%8D%E5%8A%A1%E7%BD%91%E5%85%B3)\n            - [分布式配置](#%E5%88%86%E5%B8%83%E5%BC%8F%E9%85%8D%E7%BD%AE)\n            - [服务熔断](#%E6%9C%8D%E5%8A%A1%E7%86%94%E6%96%AD)\n            - [链路跟踪](#%E9%93%BE%E8%B7%AF%E8%B7%9F%E8%B8%AA)\n            - [服务监控](#%E6%9C%8D%E5%8A%A1%E7%9B%91%E6%8E%A7)\n        - [Cloud Native 架构选型](#cloud-native-%E6%9E%B6%E6%9E%84%E9%80%89%E5%9E%8B)\n            - [CNCF 架构体系](#cncf-%E6%9E%B6%E6%9E%84%E4%BD%93%E7%B3%BB)\n            - [Spring Cloud 架构体系](#spring-cloud-%E6%9E%B6%E6%9E%84%E4%BD%93%E7%B3%BB)\n            - [Dubbo 架构体系](#dubbo-%E6%9E%B6%E6%9E%84%E4%BD%93%E7%B3%BB)\n        - [Dubbo Cloud Native 准备](#dubbo-cloud-native-%E5%87%86%E5%A4%87)\n            - [Dubbo 注解驱动（Annotation-Driven）](#dubbo-%E6%B3%A8%E8%A7%A3%E9%A9%B1%E5%8A%A8annotation-driven)\n                - [`@DubboComponentScan` 服务端示例](#dubbocomponentscan-%E6%9C%8D%E5%8A%A1%E7%AB%AF%E7%A4%BA%E4%BE%8B)\n                - [`@DubboComponentScan` 客户端示例](#dubbocomponentscan-%E5%AE%A2%E6%88%B7%E7%AB%AF%E7%A4%BA%E4%BE%8B)\n            - [Dubbo 外部化配置（Externalized Configuration）](#dubbo-%E5%A4%96%E9%83%A8%E5%8C%96%E9%85%8D%E7%BD%AEexternalized-configuration)\n        - [现场演示环节](#%E7%8E%B0%E5%9C%BA%E6%BC%94%E7%A4%BA%E7%8E%AF%E8%8A%82)\n            - [Dubbo 整合 Hystrix 示例](#dubbo-%E6%95%B4%E5%90%88-hystrix-%E7%A4%BA%E4%BE%8B)\n                - [Dubbo 客户端实现](#dubbo-%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%AE%9E%E7%8E%B0)\n                    - [Dubbo 服务端实现](#dubbo-%E6%9C%8D%E5%8A%A1%E7%AB%AF%E5%AE%9E%E7%8E%B0)\n                - [测试客户端 REST 服务](#%E6%B5%8B%E8%AF%95%E5%AE%A2%E6%88%B7%E7%AB%AF-rest-%E6%9C%8D%E5%8A%A1)\n    - [参考资源](#%E5%8F%82%E8%80%83%E8%B5%84%E6%BA%90)\n\n<!-- /TOC -->\n\n## 分享简介\n\nCloud Native 应用架构随着云技术的发展受到业界特别重视和关注，尤其是 CNCF（Cloud Native Computing Foundation）项目蓬勃发展之际。Dubbo 作为服务治理的标志性项目，自然紧跟业界的潮流，拥抱技术的变化。本次分享的议题包括介绍 Apache 孵化项目Dubbo Spring Boot Project 以及汇报 Dubbo 与 Cloud Native 整合过程中的一些实践与思考，如适配  Spring Cloud 、服务发现、服务网关、服务跟踪以及监控等。\n\n> 注：为了读者的阅读方便和习惯，本文字稿将在演讲内容的基础上做出适当的调整。\n\n\n\n## 自我介绍\n\n马昕曦（小马哥），阿里巴巴中间件技术专家，十余年 Java EE 从业经验，Dubbo 维护者、架构师以及微服务布道师。目前主要负责阿里巴巴集团微服务技术实施、架构衍进、基础设施构建等。重点关注云计算、微服务以及软件架构等领域。通过 SUN Java（SCJP、SCWCD、SCBCD）以及 Oracle OCA 等的认证。\n\n\n\n## 主要议程\n\n今天我非常荣幸地与大家一起讨论关于 Dubbo Cloud Native 相关议题，本次议题紧扣“实践与思考“两个关键字，主要的议程包括：\n* **Cloud Native 基础设施**\n* **Cloud Native 架构选型** \n* **Dubbo Cloud Native 准备**\n\n\n\n\n\n### Cloud Native 基础设施\n\n关于 Cloud Native 的定义，不同的云平台可能给出的内容存在差异。此处，我向大家介绍目前最热门的 CNCF 的定义：\n\n>  ”[CNCF Cloud Native Definition v1.0](https://github.com/cncf/toc/blob/master/DEFINITION.md)“ 中的描述：\n>\n> > Cloud native technologies empower organizations to build and run scalable applications in modern, dynamic environments such as public, private, and hybrid clouds. Containers, service meshes, microservices, immutable infrastructure, and declarative APIs exemplify this approach.\n\n相对于其他学术流派，CNCF 的 Cloud Native 定义更为具体，偏向于软件技术。这一点我们从文中的一些关键字能够明显地体会到，如关键字 \"Containers（容器）\"、\"service meshes\"、”microservices（微服务）“等。通常，开发人员较为关注的 Cloud Native 基础设施为：“服务发现”、“负载均衡”、“服务网关”、“分布式配置”、“服务熔断”以及“跟踪监控”，如图所示：\n\n![幻灯片05](/img/assets/幻灯片05.jpg)\n\n由于 PPT 格式的限制，此处我将“链路跟踪”与“服务监控” 并陈为“跟踪监控”。接下来，我们进入“服务发现”的讨论。\n\n\n\n\n\n#### 服务发现（Service Discovery ）\n\n随着微服务架构（MSA）受到不同规模企业的青睐，服务治理的实施逐渐被提上基础设施改造的议程。尽管这些概念在 SOA 时代已经提出，然而引起业界广泛关注应归功于微服务。服务发现（Service Discovery ）作为服务治理的核心特性，通常也将服务注册（Service Registration）一并讨论。无论是服务发现，还是服务注册，在具体落地实施时，它们必须面对技术选型的问题。在座的各位，包括我，大多数是 Java 程序员，自然关心 Java 的技术方案。目前，Java 社区最为津津乐道的方案莫过于 Spring Cloud，搭配 Netflix OSS 组件 Eureka，帮助 Spring Boot 应用快速搭建服务发现体系。其中，Eureka Server 作为注册中心服务器，Spring Boot 应用整合 Eureka Client 向 Eureka Server 注册。实际上，Spring Cloud 除了整合 Netflix Eureka 作为服务发现之外，还提供了 Apache Zookeeper 和 HachiCorp Consul 的实现，所以这三种方案出现在当前页面：\n\n![幻灯片06](/img/assets/幻灯片06.jpg)\n\n\n\n其中还包括 Redis 和 Apache Curator，前者是 Dubbo 的服务发现实现方案之一，然而小马哥并不建议使用 Redis 作为注册中心，还是保持它缓存中间件的单纯性较好。而 Curator 作为 Zookeeper Java 客户端类库，它不但可用在 Dubbo，而且其扩展项目 Curator Service Discovery 也是 Spring Cloud 整合 Zookeeper 作为服务发现的关键基础设施。或许大家思考以上方案应该如何选型的问题。\n\n\n\n##### 如何选择\n\n\n\n###### Eureka\n\n当服务发现选型时，Netflix Eureka 或许是在开发人员脑海中复现的首选方案。然而 Eureka 在阿里大规模实践时，它的表现并不理想，当 Eureka 客户端服务实例数量达到一定时，Eureka Server 时常会出现服务不可用的情况，主要的问题集中在更新（Update）机制、复制（Replication）机制以及内存型存储。由于时间的关系，此处我不加详细说明，部分答案在 Eureka Wiki [Eureka 2.0 Motivations](https://github.com/Netflix/eureka/wiki/Eureka-2.0-Motivations) 中也有描述：\n\n> [Why Eureka 2.0?](https://github.com/Netflix/eureka/wiki/Eureka-2.0-Motivations#why-eureka-20)\n>\n> * Only support homogenous client views\n> * Only supports scheduled updates\n> * Replication algorithm limits scalability\n>\n> > 注：以上具体内容在分享现场并没有具体提及，此处特意为读者补充。\n\n\n\n以上问题 Netflix 早在 2015 年已意识到，然而 Eureka 2.0 的发布遥遥无期。后来，我托朋友联系上了 Netflix 的工程师，咨询他们关于 Eureka 1 在自身生产环境的使用情况。他们的回复是部分场景在使用。这样的答复值得玩味，再细问其覆盖比重，对方三缄其口。这不得不让我对 Eureka 的成熟度产生了质疑，所以我不建议大家在数以千计的应用实例场景中使用。\n\n\n\n\n\n###### Consul\n\nConsul 同样作为 Spring Cloud 服务中心，基于 GO 语言开发，其数据一致性采用 Raft 算法，低内存，集群支持。曾一度成为我理想的替换 Eureka 的方案，不过本人并不具备 Consul 的大规模运用，为此还特意请教永辉云创的架构师翟永超（《Spring Cloud 微服务实战》的作者）。他告知 Consul 表现不错，并在跨 DC（数据中心）方面也比较稳定：![image-20180627210416358](/img/assets/image-20180627210416358.png)\n\n他的答复让我增强了 Consul 的信心，稍显遗憾的是其 Consul 应用节点略少。后来，我听说 B 站的哥们自研服务发现中间件 **[discovery](https://github.com/Bilibili/discovery/)**，他们应该也对 Consul 做过调研和评估，他们的看法是：\n\n![image-20180627210529749](/img/assets/image-20180627210529749.png)\n\n> Github 开源地址：https://github.com/Bilibili/discovery/\n>\n> **[discovery](https://github.com/Bilibili/discovery/)** 在 B 站 K8S 上的使用情况：\n>\n> ![image-20180627210509857](/img/assets/image-20180627210509857.png)\n\n综合两家公司的评估，尽管没有经过本人实际操作，并且两者没有提供具体的数据指标，然而在一定程度上说明 Consul 作为注册中心的实例节点规模大概在 2k 以内。换言之，它比较适合中小型企业。\n\n\n\n###### Zookeeper\n\nZookeeper 即可是 Spring Cloud 注册中心，又能作为 Dubbo 注册中心，与 Eureka 不同，它属于 CP 分布式策略，而后者属于 AP。两者的共同点在于均属于内存型注册中心，在大规模集群场景，也会遇到 Eureka 类似的问题。不过从运维的角度，相较于 Eureka 而言，熟悉 Zookeeper 运维朋友更多。在生态性方面，Zookeeper 周边的生态更丰富，如 Zookeeper C API，尽管 Eureka 提供了语言无关性的 REST 接口。同时，Zookeeper 还从当配置服务器的角色，降低了学习的成本。综上结论，我推荐使用 Zookeeper 作为服务发现基础设施，无论您选择 Dubbo 方案，还是使用 Spring Cloud。尽管它在大规模集群时也出现 Zookeeper 间歇性卡顿等问题。\n\n\n\n\n\n#### 负载均衡\n\n![幻灯片07](/img/assets/幻灯片07.jpg)\n\n负载均衡是第二个重要 Cloud Native 基础设施，熟悉 Spring Cloud 的朋友一定对右侧的蝴蝶结有印象，它就是 Netflix OSS 负载均衡组件 Ribbon，框架层面提供了多种负载均衡规则，如：\n\n* **随机** - `RandomRule`\n* **轮循** - `RoundRobinRule`\n* **权重响应时间** - `WeightedResponseTimeRule`\n\n`WeightedResponseTimeRule` 之外，其他的 Ribbon 负载均衡实现均没有提供权重因子，而权重因子对于蓝绿发布、服务预热等方面的帮助是至关重要的。因此，权重因子在 Dubbo “**随机**“、”**轮询**“ 以及 ”**最少活跃调用数**“ 负载均衡算法中均体现。\n\n以上讨论的两种框架均属于 Java 实现，而中间的 Kong 则是更为通用的实现，通常它作为 API 服务网关，后面我们将继续讨论。可简单地认为它是 Nginx + Lua 的扩展，负载均衡自然成为不可或缺的特性。其默认的负载均衡算法为具备权重的轮询（weighted-round-robin），同时一致性 Hash 算法作为可选方案。\n\n\n\n#### 服务网关\n\n![幻灯片08](/img/assets/幻灯片08.jpg)\n\n谈及服务网关，Java 工程师最容易想到的是 Spring Cloud Zuul。Zuul 是 Netflix 基于 Servlet API 开发的 Web 服务代理组件，在 Spring Cloud 使用场景中，它与 Eureka  和 Ribbon 整合，打造具备服务动态更新和负载均衡能力的服务网关。\n\n最近，随着 Spring Cloud Finchley 的发布，Spring Cloud Zuul 的替代方案 Spring Cloud Gateway 孕育而生，不过官方的描述还是比较谦虚谨慎，并没有一刀切地引导开发人员从 Zuul 迁移到 Gateway 上来：\n\n> API Gateway built on top of the Spring Ecosystem, including: Spring 5, Spring Boot 2 and Project Reactor. Spring Cloud Gateway aims to provide a simple, yet effective way to route to APIs and provide cross cutting concerns to them such as: security, monitoring/metrics, and resiliency.\n\n两者不同点在于，Zuul 运行在 Servlet 容器中，而 Gateway 并不像 Spring WebFlux 能够兼容 Servlet 3.1 运行时，而是必须依赖 Netty 的运行时，以及整合 Reactive 框架 Reactor，实现异步非阻塞网关。由于近期对于 Spring 5  WebFlux 能够大幅提升应用性能的观点甚嚣尘上，实际上，没有任何直接性能基准测试证明 WebFlux 能够加快程序执行速度，或许大家认为我的观点与主流格格不入，可是我要告诉大家的是，这个问题我在同事间验证过很多次，大多数情况，Reactive 并不没有提升性能。就连 Spring 官方也承认这个观点：\n\n> [1.1.7. Performance vs scale](https://docs.spring.io/spring/docs/5.0.7.RELEASE/spring-framework-reference/web-reactive.html#webflux-performance)\n>\n> Performance has many characteristics and meanings. **Reactive and non-blocking generally do not make applications run faster**. They can, in some cases, for example if using the `WebClient` to execute remote calls in parallel. **On the whole it requires more work to do things the non-blocking way and that can increase slightly the required processing time**.\n>\n> > 资源地址：https://docs.spring.io/spring/docs/5.0.7.RELEASE/spring-framework-reference/web-reactive.html#webflux-performance\n\n同时，这里提供一篇 [Spring 5 WebFlux: Performance tests](https://blog.ippon.tech/spring-5-webflux-performance-tests/) 的文章，在结尾部分给出了结论，作者坦言在速度上没有明显的提升，甚至从结果来看，速度稍微更糟糕：\n\n- *No improvement in speed was observed with our reactive apps (the Gatling results are even slightly worse).*\n\n> 以上测试工程和结论是由开源项目 JHipster 的工程师给出，具备一定的客观性和可信度。\n>\n> > 资源地址：https://blog.ippon.tech/spring-5-webflux-performance-tests/\n\n换言之，基于 Reactor 开发的 Gateway 在性能可能并没有明显的提升。因此，Zuul 和 Gateway 的性能对比则演变为 Servlet 容器和 Netty Web 容器的比较，感兴趣的朋友可以去网上寻找一些比较数据，两者的性能在伯仲间。\n\n当然，我和在座的各位一样，对 Java 的实现方案自然是情有独钟。然而我想说的是，身为 Java 工程师，**眼中难免有 Java，但是眼中不要只有 Java**。Nginx 作为当年著名 “C10K” 问题的解决方案，无论从连接数量，还是资源消耗方面均优于 Java 实现。作为技术人，应该具有更为宽广的胸怀，接纳非我族类的气魄，该放手的时候就放手。Nginx 作为服务网关不失为一种好的方案，然而它的动态性略为不足，需要结合 Lua 脚本辅助完成，因此，OpenResty 和 Kong 这类方案脱颖而出。如果就 HTTP API 网关而言，个人认为 Kong 的方案更佳，因为它提供完整的解决方案，包括前面讨论的负载均衡（权重）、服务熔断以及服务发现等特性。类似的特性在 CNCF 项目 Envoy 也有体现，它是另一种高性能代理的方案，提供服务发现、健康和负载均衡。在协议上，天然支持 HTTP 和 HTTP/2，而通讯协议支持 gRPC，建议大家予以高度关注。\n\n值得一提的是，HTTP API 网关通常需要支持 sidecar，换言之，支撑网关服务的基础设施必须提供服务发现的能力，就功能性而言，Zuul 和 Gateway 自身并不具备这样的特性，需要搭配 Eureka 这样组件，它们更像服务路由器的角色。\n\n\n\n\n\n#### 分布式配置\n\n![幻灯片09](/img/assets/幻灯片09.jpg)\n\n左边和中间的四种技术均为 Spring Cloud 分布式配置的底层存储，其中 Git 为版本式配置，而 JDBC 是从 Spring Cloud Edgware 版本开始支持，提供更为通用和动态的配置源。这里我们又见到 Zookeeper 的声影，从简化运维的角度，可以利用 Zookeeper 即承担服务发现，也作为分布式配置的基础设施。而最右边的 etcd 是最近非常火的 Kubernetes 分布式配置的 key-value 存储，提供快速、简单、安全和可高的解决方案。\n\n\n\n\n\n#### 服务熔断\n\n![幻灯片10](/img/assets/幻灯片10.jpg)\n\n服务熔断也非常让开发人员联想到 Spring Cloud Hystrix 技术，不过 Hystrix 并非与 Spring Cloud 强耦合，当然 Dubbo 也能结合 Netflix Hystrix 框架提供服务熔断的能力，后面部分将介绍 Dubbo 与 Hystrix 整合，提升 Dubbo 服务熔断的能力。确切地说，Dubbo 所提供的能力是集群容错，包括 Failover 等模式。 Kong 也天然地支持服务熔断的能力，所以它作为 API 网关的特性是全面的。\n\n\n\n\n\n#### 链路跟踪\n\n![幻灯片11](/img/assets/幻灯片11.jpg)\n\n以上链路跟踪的基础设施从左至右，分别为 Zipkin、OpenTracing 以及 Jaeger，三者的灵感均来自于 [Google 论文 Dapper](https://ai.google/research/pubs/pub36356)。相对而言，Java 程序员可能更为熟悉 Zipkin，因为它是 Spring Cloud Sleuth 首选方案，提供客户端上报以及服务端聚合和 Dashboard 等功能。而 OpenTracing 和 Jaeger 是 CNCF 孵化项目，前者属于开放的标准，提供多语言的适配实现，后者则由 Uber（优步）公司开发并开源的链路跟踪项目，功能上与 Zipkin 类似，不过它基于 GO 语言开发，同时也提供 Java 客户端。\n> OpenTracing 官网：http://opentracing.io/\n> jaeger 官网：https://www.jaegertracing.io/\n\n\n\n\n\n#### 服务监控 \n\n![幻灯片12](/img/assets/幻灯片12.jpg)\n\n服务监控与链路跟踪有所区别，主要用于监控应用系统或业务的指标数据，可能是健康阈值，如 CPU 或 内存使用率，也可以是业务指标，如最近一小时的用户登录量。通常采用 Metrics 方式暴露，可使用客户端推送或服务端拉取的方式传输 Metrics 信息到数据中心。通常 Metrics 数据与时间是存在对应关系，因此，基本上采用时序型数据库来存储，如图中的 OpenTSDB。通常，Java 微服务应用会选择 Spring Boot 框架作为基础设施，如我之前设计的监控架构就采用了 Spring Boot + OpenTSDB ，后端存储基于 HBase。当时 Spring Boot Actuator Metrics 仅为简单的 Key Value 形式，自然 OpenTSDB 是理想的选择。随着 Spring Boot 2.0 开始支持 [Micrometer](https://micrometer.io/) 之后，使得 Spring Boot 的应用能够整合更多的 Micrometer 适配方案，其中名气较大的就是图中间的 [Prometheus](https://prometheus.io/)，它同样也是 CNCF 的孵化项目。\n\n当然服务监控不只是 Metrics 方式，我所知道国内不少的公司采用了日志收集的方案，并搭配 ELK（Elasticsearch, Logstash, Kibana） 架构，减少运维成本。假设您没有使用该方案，或者仅使用了 Elasticsearch 的话，无论哪种方案，图形化界面的监控是必不可少的，因此我推荐 Grafana，该项目能够支持多种数据源，包括前文提到的 OpenTSDB、Prometheus 以及 ElasticSearch 等。由此，从数据采集、上报、聚合以及展示的特性上，这些基础设施帮助 Cloud Native 应用构建服务监控的闭环。\n\n本议程介绍了一些 Cloud Native 技术设施，接下里我们继续讨论 Cloud Native 架构选型。\n\n\n\n\n\n### Cloud Native 架构选型\n\n\n#### CNCF 架构体系\n\n![CNCF](/img/assets/CNCF.png)\n\nCNCF 体系作为目前最热门的架构选型之一，基本上围绕着 Kubernetes 为中心而构建。个人认为，Java 业界和 CNCF 体系并没有达成共识，如服务网关，CNCF 主打 Envoy，而 Java 主要的方案为 Zuul 和 Spring Cloud Gateway。因此，个人建议是密切的关注 CNCF 的发展，不过个别孵化项目可以先行，如 Prometheus 和 Jaeger 等。 至于 CNCF 与 Java 生态的整合和落地，还得有待时日。\n\n\n\n\n\n#### Spring Cloud 架构体系\n\n![Spring Cloud](/img/assets/Spring-Cloud.png)\n\n实际上，这个图片并非 Spring Cloud 组件架构，而是将其整合在 Pivotal Cloud Foundry (PCF) 架构中。基本上，Spring Cloud 功能组件均有所体现，包括 Eureka、Hystrix、Ribbon 等。不过值得注意的是，Spring Cloud Stream 是一套较为完整和抽象的流式编程框架，屏蔽了底层传输介质（不仅是消息服务），如 Kafka、RabbitMQ 等。除此之外，其他的组件可圈可点，如 Eureka 在大规模运用中的卡顿问题、Ribbon 缺少权重、Zuul 连接数限制和资源消耗、服务调用受限于 Feign REST 协议限制等。如果在小规模场景使用，以上限制或问题不明显，可以说 Spring Cloud 完全能够适任。\n\n不过，差不多两年前，我曾在不同的公开场合讲过：”Spring Boot 易学难精，Spring Cloud 能用但不成熟“。当时很多人觉得我“离经叛道”，然而这句话并非空穴来风，是我这几年来 Java 微服务架构实施的心得。这两年来，深受 Spring Cloud “折磨”的小伙伴逐渐觉醒，慢慢地开始回到 Dubbo 等技术方案。如 Martin Fowler 在为“微服务”下定义时，提到通讯协议要用轻量级的 REST。假设微服务要做到服务无关的话，那么 Web Services 协议也是可以，尽管它看起来比较重，不过 Web Services 的结构化和强类型，可以省去不少的运行时校验逻辑。在我看来，微服务更大程度应该体现在服务粒度上，诚如 Netflix 前架构师 Adrian Cockcroft 说言：“Fine grain SOA”（微服务就是细粒度的 SOA），就这一点而言，比较容易地和业界达成共识。当我们把 Martin 的话视如圭臬时，我们是否要思考它是否经得起工程检验。这里，我没有兴趣贬低他人，来抬高自己（Dubbo），从而引导让大家放弃 Spring Cloud，而是我们需要给 Spring Cloud 时间，包括未来 Dubbo 也会向 Spring Cloud 靠拢并整合。在阿里的内部，基于 Nacos（马上开源的项目）和 Apache RocketMQ，实现了 Spring Cloud Service Discovery、Config 以及 Stream 等整合和适配，一旦时机成熟，可能会开源与大家共建。\n\n既然谈到了 Dubbo，下面我们再来讨论 Dubbo 的架构体系。\n\n\n\n\n#### Dubbo 架构体系\n\n![Dubbo.png](/img/assets/Dubbo.png)\n\n编程模型方面，不但支持传统的 Spring XML 配合方式，已经实现注解驱动以及外部化配置，并且全面支持最新的 Spring Boot 2.0，在不久的未来，大家会看到 Dubbo 与 Spring Cloud 的整合，使开发人员无缝地衔接已有的 Spring Cloud 应用。\n> Dubbo Spring Boot 项目地址：https://github.com/apache/incubator-dubbo-spring-boot-project\n\n注册中心方面，Dubbo 将整合 Eureka、etcd 以及 Consul 基础设施，深度与业界热门方案整合。\n\n熔断机制方面，Dubbo 会在近期发布 Hystrix 整合实现，将编程友好性做得最大化。\n\n通讯协议方面，Dubbo 将会支持 gRPC、Thrift 等热门通讯协议。\n\n至于序列化协议，自然首先考虑的是 Protobuf，因其高层 gRPC 搭配 HTTP/2 快成或已经成为下一代通讯协议的事实标准，使得任何人无法忽视它们的存在。当然其他协议也会陆续支持。\n\n其他方面，我这里就不一一介绍，总之，现在 Dubbo 已不再只是一个单一的 PRC 框架，而是要拥抱业界，形成完整的生态体系，与业界形成最大公约数。\n\n\n\n\n\n### Dubbo Cloud Native 准备\n\n![幻灯片16](/img/assets/幻灯片16.jpg)\n\n在 Dubbo 架构体系时，我们曾提到编程模型的变化。从 Dubbo `2.5.8` 开始，注解驱动和外部化配置均已得到支持。同时，Dubbo 已经合并 Dubbox 代码，Java JAX-RS 标准得到了支持，目前业界事实的 REST 标准 Spring Web MVC 正在同步开发。Reactive 的支持也在同步进行，小马哥还得友好地提醒一下各位，对于 Reactive 的期望不应该过分的关注性能的提升。\n\n\n\n\n#### Dubbo 注解驱动（Annotation-Driven）\n\n\n在 Dubbo  `2.5.7` 之前的版本 ，Dubbo 提供了两个核心注解 `@Service` 以及 `@Reference`，分别用于Dubbo 服务提供和 Dubbo 服务引用。\n\n其中，`@Service` 作为 XML 元素 `<dubbo:service>` 的替代注解，与 Spring Framework `@org.springframework.stereotype.Service` 类似，用于服务提供方 Dubbo 服务暴露。与之相对应的 `@Reference`，则是替代`<dubbo:reference>` 元素，类似于 Spring 中的 `@Autowired`。\n\n `2.5.7` 之前的Dubbo，与早期的 Spring Framework 2.5 存在类似的不足，即注解支持不够充分。注解需要和 XML 配置文件配合使用，如下所示：\n\n```xml\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<beans xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n       xmlns:dubbo=\"http://code.alibabatech.com/schema/dubbo\"\n       xmlns=\"http://www.springframework.org/schema/beans\"\n       xsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd\n\thttp://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd\">\n\n    <dubbo:application name=\"annotation-provider\"/>\n    <dubbo:registry address=\"127.0.0.1:4548\"/>\n    <dubbo:annotation package=\"com.alibaba.dubbo.config.spring.annotation.provider\"/>\n\n</beans>\n```\n\n不仅如此，当时的版本存在“ `@Service` Bean 不支持 Spring AOP” 以及 “`@Reference` 不支持字段继承性” 等问题。\n\n从 `2.5.7` 开始，Dubbo 开始引入组件扫描 Annotation `@DubboComponentScan`，借鉴了 Spring Boot 1.3 引入的 `@ServletComponentScan`。\n\n在职责上，`@DubboComponentScan` 相对于 Spring Boot `@ServletComponentScan` 更为繁重，原因在于处理 Dubbo  `@Service` 类暴露 Dubbo 服务外，还有帮助 Spring  Bean `@Reference`字段或者方法注入 Dubbo 服务代理。 \n\n在场景上，Spring Framework `@ComponentScan` 组件扫描逻辑更为复杂。而在 `@DubboComponentScan`  只需关注 `@Service` 和 `@Reference` 处理。\n\n\n> 注：更多 Dubbo 注解驱动的详情，请参考[《Dubbo 注解驱动（Annotation-Driven）》](https://github.com/mercyblitz/blogs/blob/master/java/dubbo/Dubbo-Annotation-Driven.md)\n\n\n##### `@DubboComponentScan` 服务端示例\n\n假设，服务提供方和服务消费分均依赖服务接口`DemoService`:\n\n```java\npackage com.alibaba.dubbo.demo;\n\npublic interface DemoService {\n\n    String sayHello(String name);\n\n}\n```\n\n* 服务提供方实现`DemoService`  - `AnnotationDemoService` \n\n同时标注 Dubbo `@Service` ：\n\n```java\npackage com.alibaba.dubbo.demo.provider;\n\nimport com.alibaba.dubbo.config.annotation.Service;\nimport com.alibaba.dubbo.demo.DemoService;\n\n/**\n * Annotation {@link DemoService} 实现\n *\n * @author <a href=\"mailto:mercyblitz@gmail.com\">Mercy</a>\n */\n@Service\npublic class AnnotationDemoService implements DemoService {\n\n    @Override\n    public String sayHello(String name) {\n        return \"Hello , \" + name;\n    }\n\n}\n```\n\n* 服务端 `@Configuration` Class\n\n```java\npackage com.alibaba.dubbo.demo.config;\n\nimport com.alibaba.dubbo.config.ApplicationConfig;\nimport com.alibaba.dubbo.config.ProtocolConfig;\nimport com.alibaba.dubbo.config.RegistryConfig;\nimport com.alibaba.dubbo.config.spring.context.annotation.DubboComponentScan;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * 服务提供方配置\n *\n * @author <a href=\"mailto:mercyblitz@gmail.com\">Mercy</a>\n */\n@Configuration\n@DubboComponentScan(\"com.alibaba.dubbo.demo.provider\") // 扫描 Dubbo 组件\npublic class ProviderConfiguration {\n\n    /**\n     * 当前应用配置\n     */\n    @Bean(\"dubbo-annotation-provider\")\n    public ApplicationConfig applicationConfig() {\n        ApplicationConfig applicationConfig = new ApplicationConfig();\n        applicationConfig.setName(\"dubbo-annotation-provider\");\n        return applicationConfig;\n    }\n\n    /**\n     * 当前连接注册中心配置\n     */\n    @Bean(\"my-registry\")\n    public RegistryConfig registryConfig() {\n        RegistryConfig registryConfig = new RegistryConfig();\n        registryConfig.setAddress(\"N/A\");\n        return registryConfig;\n    }\n\n    /**\n     * 当前连接注册中心配置\n     */\n    @Bean(\"dubbo\")\n    public ProtocolConfig protocolConfig() {\n        ProtocolConfig protocolConfig = new ProtocolConfig();\n        protocolConfig.setName(\"dubbo\");\n        protocolConfig.setPort(12345);\n        return protocolConfig;\n    }\n}\n```\n\n* 服务提供方引导类\n\n```java\npackage com.alibaba.dubbo.demo.bootstrap;\n\nimport com.alibaba.dubbo.demo.DemoService;\nimport com.alibaba.dubbo.demo.config.ProviderConfiguration;\nimport org.springframework.context.annotation.AnnotationConfigApplicationContext;\n\n/**\n * 服务提供方引导类\n *\n * @author <a href=\"mailto:mercyblitz@gmail.com\">Mercy</a>\n */\npublic class ProviderBootstrap {\n\n    public static void main(String[] args) {\n        // 创建 Annotation 配置上下文\n        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();\n        // 注册配置 Bean\n        context.register(ProviderConfiguration.class);\n        // 启动上下文\n        context.refresh();\n        // 获取 DemoService Bean\n        DemoService demoService = context.getBean(DemoService.class);\n        // 执行 sayHello 方法\n        String message = demoService.sayHello(\"World\");\n        // 控制台输出信息\n        System.out.println(message);\n    }\n    \n}\n```\n\n\n\n\n##### `@DubboComponentScan` 客户端示例\n\n\n* 消费服务 `DemoService`\n\n```java\npackage com.alibaba.dubbo.demo.consumer;\n\nimport com.alibaba.dubbo.config.annotation.Reference;\nimport com.alibaba.dubbo.demo.DemoService;\n\n/**\n * Annotation 驱动 {@link DemoService} 消费方\n *\n * @author <a href=\"mailto:mercyblitz@gmail.com\">Mercy</a>\n */\npublic class AnnotationDemoServiceConsumer {\n\n    @Reference(url = \"dubbo://127.0.0.1:12345\")\n    private DemoService demoService;\n\n    public String doSayHell(String name) {\n        return demoService.sayHello(name);\n    }\n}\n```\n\n* 消费端 `@Configuration` Class\n\n与服务提供方配置类似，服务消费方也许 Dubbo 相关配置 Bean - `ConsumerConfiguration`\n\n\n```java\npackage com.alibaba.dubbo.demo.config;\n\nimport com.alibaba.dubbo.config.ApplicationConfig;\nimport com.alibaba.dubbo.config.RegistryConfig;\nimport com.alibaba.dubbo.config.spring.context.annotation.DubboComponentScan;\nimport com.alibaba.dubbo.demo.consumer.AnnotationDemoServiceConsumer;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * 服务消费方配置\n *\n * @author <a href=\"mailto:mercyblitz@gmail.com\">Mercy</a>\n */\n@Configuration\n@DubboComponentScan\npublic class ConsumerConfiguration {\n\n    /**\n     * 当前应用配置\n     */\n    @Bean\n    public ApplicationConfig applicationConfig() {\n        ApplicationConfig applicationConfig = new ApplicationConfig();\n        applicationConfig.setName(\"dubbo-annotation-consumer\");\n        return applicationConfig;\n    }\n\n    /**\n     * 当前连接注册中心配置\n     */\n    @Bean\n    public RegistryConfig registryConfig() {\n        RegistryConfig registryConfig = new RegistryConfig();\n        registryConfig.setAddress(\"N/A\");\n        return registryConfig;\n    }\n\n    /**\n     * 注册 AnnotationDemoServiceConsumer，@DubboComponentScan 将处理其中 @Reference 字段。\n     * 如果 AnnotationDemoServiceConsumer 非 Spring Bean 的话，\n     * 即使 @DubboComponentScan 指定 package 也不会进行处理，与 Spring @Autowired 同理\n     */\n    @Bean\n    public AnnotationDemoServiceConsumer annotationDemoServiceConsumer() {\n        return new AnnotationDemoServiceConsumer();\n    }\n\n}\n```\n\n* 服务消费方引导类\n\n```java\npackage com.alibaba.dubbo.demo.bootstrap;\n\nimport com.alibaba.dubbo.demo.config.ConsumerConfiguration;\nimport com.alibaba.dubbo.demo.config.ProviderConfiguration;\nimport com.alibaba.dubbo.demo.consumer.AnnotationDemoServiceConsumer;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.context.annotation.AnnotationConfigApplicationContext;\n\n/**\n * 服务消费端引导类\n *\n * @author <a href=\"mailto:mercyblitz@gmail.com\">Mercy</a>\n */\npublic class ConsumerBootstrap {\n\n    public static void main(String[] args) {\n        // 启动服务提供方上下文\n        startProviderContext();\n        // 启动并且返回服务消费方上下文\n        ApplicationContext consumerContext = startConsumerContext();\n        // 获取 AnnotationDemoServiceConsumer Bean\n        AnnotationDemoServiceConsumer consumer = consumerContext.getBean(AnnotationDemoServiceConsumer.class);\n        // 执行 doSayHello 方法\n        String message = consumer.doSayHello(\"World\");\n        // 输出执行结果\n        System.out.println(message);\n    }\n\n    /**\n     * 启动并且返回服务消费方上下文\n     *\n     * @return AnnotationConfigApplicationContext\n     */\n    private static ApplicationContext startConsumerContext() {\n        // 创建服务消费方 Annotation 配置上下文\n        AnnotationConfigApplicationContext consumerContext = new AnnotationConfigApplicationContext();\n        // 注册服务消费方配置 Bean\n        consumerContext.register(ConsumerConfiguration.class);\n        // 启动服务消费方上下文\n        consumerContext.refresh();\n        // 返回服务消费方 Annotation 配置上下文\n        return consumerContext;\n    }\n\n    /**\n     * 启动服务提供方上下文\n     */\n    private static void startProviderContext() {\n        // 创建 Annotation 配置上下文\n        AnnotationConfigApplicationContext providerContext = new AnnotationConfigApplicationContext();\n        // 注册配置 Bean\n        providerContext.register(ProviderConfiguration.class);\n        // 启动服务提供方上下文\n        providerContext.refresh();\n    }\n\n}\n```\n\n\n#### Dubbo 外部化配置（Externalized Configuration）\n\n在Dubbo 注解驱动例子中，无论是服务提供方，还是服务消费方，均需要转配相关配置Bean：\n\n```java\n    @Bean\n    public ApplicationConfig applicationConfig() {\n        ApplicationConfig applicationConfig = new ApplicationConfig();\n        applicationConfig.setName(\"dubbo-annotation-consumer\");\n        return applicationConfig;\n    }\n```\n\n虽然实现类似于`ProviderConfiguration` 和 `ConsumerConfiguration` 这样的 Spring  `@Configuration` Bean 成本并不高，不过通过 Java Code 的方式定义配置 Bean，或多或少是一种 Hard Code（硬编码）的行为，缺少弹性。\n\n尽管在 Spring 应用中，可以通过 `@Value` 或者 `Environment` 的方式获取外部配置，其代码简洁性以及类型转换灵活性存在明显的不足。因此，Spring Boot  提出了外部化配置（External Configuration）的感念，即通过程序以外的配置源，动态地绑定指定类型。\n\n随着 Spring Boot / Spring Cloud 应用的流行，开发人员逐渐地接受并且使用 Spring Boot 外部化配置（External Configuration），即通过 `application.properties` 或者 `bootstrap.properties` 装配配置 Bean。\n\n下列表格记录了 Dubbo 内置配置类：\n\n| 配置类              | 标签                   | 用途         | 解释                                                                    |\n| ------------------- | ---------------------- | ------------ | ----------------------------------------------------------------------- |\n| `ProtocolConfig`    | `<dubbo:protocol/>`    | 协议配置     | 用于配置提供服务的协议信息，协议由提供方指定，消费方被动接受            |\n| `ApplicationConfig` | `<dubbo:application/>` | 应用配置     | 用于配置当前应用信息，不管该应用是提供者还是消费者                      |\n| `ModuleConfig`      | `<dubbo:module/>`      | 模块配置     | 用于配置当前模块信息，可选                                              |\n| `RegistryConfig`    | `<dubbo:registry/>`    | 注册中心配置 | 用于配置连接注册中心相关信息                                            |\n| `MonitorConfig`     | `<dubbo:monitor/>`     | 监控中心配置 | 用于配置连接监控中心相关信息，可选                                      |\n| `ProviderConfig`    | `<dubbo:provider/>`    | 提供方配置   | 当 ProtocolConfig 和 ServiceConfig 某属性没有配置时，采用此缺省值，可选 |\n| `ConsumerConfig`    | `<dubbo:consumer/>`    | 消费方配置   | 当 ReferenceConfig 某属性没有配置时，采用此缺省值，可选                 |\n| `MethodConfig`      | `<dubbo:method/>`      | 方法配置     | 用于 ServiceConfig 和 ReferenceConfig 指定方法级的配置信息              |\n| `ArgumentConfig`    | `<dubbo:argument/>`    | 参数配置     | 用于指定方法参数配置                                                    |\n\n通过申明对应的 Spring 扩展标签，在 Spring 应用上下文中将自动生成相应的配置 Bean。\n\n在 Dubbo 官方用户手册的[“属性配置”](http://dubbo.io/books/dubbo-user-book/configuration/properties.html)章节中，`dubbo.properties` 配置属性能够映射到  `ApplicationConfig` 、`ProtocolConfig` 以及 `RegistryConfig` 的字段。从某种意义上来说，`dubbo.properties`  也是 Dubbo 的外部化配置。\n\n> 注：更多外部化配置的详情，请参考[《Dubbo 外部化配置（Externalized Configuration）》](https://github.com/mercyblitz/blogs/blob/master/java/dubbo/Dubbo-Externalized-Configuration.md)\n\n\n\n\n\n### 现场演示环节\n\n> 本环境为分享后部分，现在编码 + 演示环境，当前文字仅提供代码实现。\n\n\n#### Dubbo 整合 Hystrix 示例\n\n> 本示例出现在分享议程的代码演示，将其放置此处，方便阅读理解\n\n\n\n##### Dubbo 客户端实现\n\n\n\n* 实现 `HystrixCommand`\n\n```java\npublic class ResultHystrixCommand extends HystrixCommand<Result> {\n\n    private final Invoker<?> invoker;\n\n    private final Invocation invocation;\n\n    public ResultHystrixCommand(Invoker<?> invoker, Invocation invocation) {\n        super(HystrixCommandGroupKey.Factory.asKey(\n                \"ResultHystrixCommand\"),\n                100); // 设置超时时间\n        // 关联 Dubbo Invoker 和 Invocation\n        this.invoker = invoker;\n        this.invocation = invocation;\n    }\n\n    @Override\n    protected Result run() throws Exception {\n        // 远程方法调用执行\n        return invoker.invoke(invocation);\n    }\n}\n```\n\n\n\n当目标方法执行时间超过 100 ms 时，触发熔断，并抛出 `new UnsupportedOperationException(\"No fallback available.\")`。\n\n\n\n* Dubbo  `Filter` 整合 `ResultHystrixCommand`\n\n```java\n@Activate(group = Constants.CONSUMER, value = \"hystrix\") // 命名当前 Filter 为 \"hystrix\"\npublic class HystrixFilter implements Filter {\n\n    @Override\n    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {\n        return new ResultHystrixCommand(invoker, invocation).execute();\n    }\n}\n```\n\n\n\n* 创建并配置 `Filter` SPI 配置文件\n\n在相对于 ClassPath 资源 `META-INF/dubbo/` 下创建 `com.alibaba.dubbo.rpc.Filter`，并配置如下：\n\n```properties\nhystrix=com.alibaba.boot.dubbo.demo.consumer.filter.HystrixFilter\n```\n\n\n\n* 配置 `@Reference` `filter` 属性\n\n```java\n@RestController\npublic class DemoConsumerController {\n\n    @Reference(\n            version = \"${demo.service.version}\",\n            application = \"${dubbo.application.id}\",\n            url = \"dubbo://localhost:12345\",\n            filter = \"hystrix\" // 指向 HystrixFilter 实现\n    )\n    private DemoService demoService;\n\n    @RequestMapping(\"/sayHello\")\n    public String sayHello(@RequestParam String name) {\n        return demoService.sayHello(name);\n    }\n\n}\n```\n\n\n\n###### Dubbo 服务端实现\n\n* 服务提供者实现\n\n```java\n@Service(\n        version = \"${demo.service.version}\",\n        application = \"${dubbo.application.id}\",\n        protocol = \"dubbo\",\n        registry = \"${dubbo.registry.id}\"\n)\npublic class DefaultDemoService implements DemoService {\n\n    private final Random random = new Random();\n\n    public String sayHello(String name) {\n        hold();\n        return \"Say : Hello, \" + name + \" (from Spring Boot)\";\n    }\n\n    private void hold() { // 随机等待 < 200 ms，当时间超过 100 ms 时，触发客户端熔断\n        long time = random.nextInt(200);\n        System.out.println(\"To hold \" + time + \" ms!\");\n        try {\n            Thread.sleep(time);\n        } catch (InterruptedException e) {\n            Thread.currentThread().interrupt();\n        }\n    }\n\n}\n```\n\n\n\n##### 测试客户端 REST 服务\n\n依次启动服务端 和客户端 Spring Boot 应用，\n\n* 执行 `curl` 命令\n\n```\nmercyblitz$ curl http://localhost:8080/sayHello?name=Hello\n```\n\n* 测试结果\n\n```\n{\"timestamp\":\"2018-06-23T01:33:58.682+0000\",\"status\":500,\"error\":\"Internal Server Error\",\"message\":\"ResultHystrixCommand timed-out and no fallback available.\",\"path\":\"/sayHello\"}\n```\n\n运行结果说明服务端方法执行超过 100 ms，引起客户端熔断。\n\n（EOF）\n\n## 参考资源\n\n* [Dubbo 官网](https://dubbo.apache.org/)：https://dubbo.apache.org/\n* [Dubbo 工程](https://github.com/apache/incubator-dubbo)：https://github.com/apache/incubator-dubbo\n* [Dubbo Spring Boot 工程](https://github.com/apache/incubator-dubbo-spring-boot-project)：https://github.com/apache/incubator-dubbo-spring-boot-project\n* [CNCF Landscape](https://landscape.cncf.io/)：https://landscape.cncf.io/\n* [Spring Cloud 官网](https://projects.spring.io/spring-cloud/)：https://projects.spring.io/spring-cloud/\n* [Kong 社区官网](https://konghq.com/kong-community-edition/)：https://konghq.com/kong-community-edition/\n* [Opentracing 官网](http://opentracing.io/)：http://opentracing.io/\n* [Jaeger 官网](https://www.jaegertracing.io/)：https://www.jaegertracing.io/\n* [Prometheus 官网](https://prometheus.io/)：https://prometheus.io/\n* [OpenTsdb 官网](http://opentsdb.net/)：http://opentsdb.net/\n* [Grafana 官网](https://grafana.com/)：https://grafana.com/\n* [小马哥 Github](https://github.com/mercyblitz)：https://github.com/mercyblitz "
  },
  {
    "path": "_posts/2018-07-25-Reactive Programming 一种技术 各自表述.md",
    "content": "# Reactive Programming 一种技术，各自表述\n\n\n\n## 前言\n\n作为一名 Java 开发人员，尤其是 Java 服务端工程师，对于 Reactive Programming 的概念似乎相对陌生。随着 Java 9 以及 Spring Framework 5 的相继发布，Reactive 技术逐渐开始被广大从业人员所注意，笔者作为其中一员，更渴望如何理解 Reactive Programming，以及它所带来的哪些显著的编程变化，更为重要的是，怎么将其用于实际生产环境，解决当前面临的问题。然而，随着时间的推移和了解的深入，笔者对  Reactive Programming  的热情逐渐被浇息，对它的未来保持谨慎乐观的态度。\n\n\n\n本文从理解 Reactive Programming 的角度出发，尽可能地保持理性和中立的态度，讨论 Reactive Programming 的实质。\n\n\n\n## 初识 Reactive\n\n笔者第一次接触 Reactive 技术的时间还要回溯到 2015年末，当时部分应用正使用 Hystrix 实现服务熔断，而 Hystrix 底层依赖是 RxJava 1.x，RxJava 是 Reactive 在 Java 编程语言的扩展框架。当时接触 Reactive 只能算上一种间接的接触，根据 Hystrix 特性来理解 Reactive 技术，感觉上，Hystrix 超时和信号量等特性与 Java 并发框架（J.U.C）的关系密切，进而认为 Reactive 是 J.U.C 的扩展。随后，笔者便参考了两本关于 Reactive Java 编程方面的书：《Reactive Java Programming》和《Reactive Programming with RxJava》。遗憾的是，两者尽管详细地描述 RxJava 的使用方法，然而却没有把 Reactive 使用场景讨论到要点上，如《Reactive Programming with RxJava》所给出的使用场景说明：\n\n> When You Need Reactive Programming\n>\n> Reactive programming is useful in scenarios such as the following:\n>\n> * Processing user events such as mouse movement and clicks, keyboard typing,GPS signals changing over time as users move with their device, device gyroscope signals, touch events, and so on.\n> * Responding to and processing any and all latency-bound IO events from disk or network, given that IO is inherently asynchronous ...\n> * Handling events or data pushed at an application by a producer it cannot control ...\n\n\n\n实际上，以上三种使用场景早已在 Java 生态中完全地实现并充分地实践，它们对应的技术分别是  Java AWT/Swing、NIO/AIO 以及 JMS（Java 消息服务）。那么，再谈 RxJava 的价值又在哪里呢？如果读者是初学者，或许还能蒙混过关。好奇心促使笔者重新开始踏上探索 Reactive 之旅。\n\n\n\n## 理解 Reactive\n\n2017年 Java 技术生态中，最有影响力的发布莫过于 Java 9 和 Spring 5，前者主要支持模块化，次要地提供了 Flow API 的支持，后者则将”身家性命“压在 Reactive 上面，认为 Reactive 是未来的趋势，它以 Reactive 框架 Reactor 为基础，逐步构建一套完整的 Reactive 技术栈，其中以 WebFlux 技术为最引人关注，作为替代 Servlet Web 技术栈的核心特性，承载了多年 Spring 逆转 Java EE 的初心。于是，业界开始大力地推广 Reactive 技术，于是笔者又接触到一些关于 Reactive 的讲法。 \n\n\n\n### 关于 Reactive 的一些讲法\n\n其中笔者挑选了以下三种出镜率最高的讲法：\n\n- Reactive 是异步非阻塞编程\n- Reactive 能够提升程序性能\n- Reactive 解决传统编程模型遇到的困境\n\n\n\n第一种说法描述了功能特性，第二种说法表达了性能收效，第三种说法说明了终极目地。下面的讨论将围绕着这三种讲法而展开，深入地探讨 Reactive Programming 的实质，并且理解为什么说 Reactive Programming 是”一种技术，各自表述“。\n\n同时，讨论的方式也一反常态，并不会直奔主题地解释什么 Reactive Programming，而是从问题的角度出发，从 Reactive 规范和框架的论点，了解传统编程模型中所遇到的困境，逐步地揭开 Reactive 神秘的面纱。其中 Reactive 规范是 JVM Reactive 扩展规范 [Reactive Streams JVM](https://github.com/reactive-streams/reactive-streams-jvm)，而 Reactive 实现框架则是最典型的实现：\n\n* Java 9 Flow API \n* RxJava \n* Reactor \n\n\n\n### 传统编程模型中的某些困境\n\n\n\n#### [Reactor](http://projectreactor.io/docs/core/release/reference/#_blocking_can_be_wasteful) 认为阻塞可能是浪费的\n\n> ### 3.1. Blocking Can Be Wasteful\n>\n> Modern applications can reach huge numbers of concurrent users, and, even though the capabilities of modern hardware have continued to improve, performance of modern software is still a key concern.\n>\n> There are broadly two ways one can improve a program’s performance:\n>\n> 1. **parallelize**: use more threads and more hardware resources.\n> 2. **seek more efficiency** in how current resources are used.\n>\n> Usually, Java developers write programs using blocking code. This practice is fine until there is a performance bottleneck, at which point the time comes to introduce additional threads, running similar blocking code. But this scaling in resource utilization can quickly introduce contention and concurrency problems. \n>\n> Worse still, blocking wastes resources. \n>\n> So the parallelization approach is not a silver bullet. \n\n\n\n将以上 Reactor 观点归纳如下，它认为：\n\n1. 阻塞导致性能瓶颈和浪费资源\n2. 增加线程可能会引起资源竞争和并发问题\n3. 并行的方式不是银弹（不能解决所有问题）\n\n\n\n第三点基本上是废话，前面两点则较为容易理解，为了减少理解的偏差，以下讨论将结合示例说明。\n\n\n\n##### 理解阻塞的弊端\n\n假设有一个数据加载器，分别加载配置、用户信息以及订单信息，如下图所示：\n\n* 图示\n\n![image-20180722175037967](/img/assets/DataLoader.png)\n\n- Java 实现\n\n```java\npublic class DataLoader {\n\n    public final void load() {\n        long startTime = System.currentTimeMillis(); // 开始时间\n        doLoad(); // 具体执行\n        long costTime = System.currentTimeMillis() - startTime; // 消耗时间\n        System.out.println(\"load() 总耗时：\" + costTime + \" 毫秒\");\n    }\n\n    protected void doLoad() { // 串行计算\n        loadConfigurations();    //  耗时 1s\n        loadUsers();                  //  耗时 2s\n        loadOrders();                // 耗时 3s\n    } // 总耗时 1s + 2s  + 3s  = 6s\n\n    protected final void loadConfigurations() {\n        loadMock(\"loadConfigurations()\", 1);\n    }\n\n    protected final void loadUsers() {\n        loadMock(\"loadUsers()\", 2);\n    }\n\n    protected final void loadOrders() {\n        loadMock(\"loadOrders()\", 3);\n    }\n\n    private void loadMock(String source, int seconds) {\n        try {\n            long startTime = System.currentTimeMillis();\n            long milliseconds = TimeUnit.SECONDS.toMillis(seconds);\n            Thread.sleep(milliseconds);\n            long costTime = System.currentTimeMillis() - startTime;\n            System.out.printf(\"[线程 : %s] %s 耗时 :  %d 毫秒\\n\",\n                    Thread.currentThread().getName(), source, costTime);\n        } catch (InterruptedException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static void main(String[] args) {\n        new DataLoader().load();\n    }\n\n}\n```\n\n\n\n* 运行结果\n\n```\n[线程 : main] loadConfigurations() 耗时 :  1005 毫秒\n[线程 : main] loadUsers() 耗时 :  2002 毫秒\n[线程 : main] loadOrders() 耗时 :  3001 毫秒\nload() 总耗时：6031 毫秒\n```\n\n\n\n\n\n* 结论\n\n由于加载过程串行执行的关系，导致消耗实现线性累加。Blocking 模式即串行执行 。\n\n\n\n不过 Reactor 也提到，以上问题可通过并行的方式来解决，不过编写并行程序较为复杂，那么其中难点在何处呢？\n\n\n\n##### 理解并行的复杂\n\n再将以上示例由串行调整为并行，如下图所示：\n\n* 图示\n\n![image-20180722175519583](/img/assets/ParallelDataLoader.png)\n\n\n\n- Java 代码\n\n```java\npublic class ParallelDataLoader extends DataLoader {\n\n    protected void doLoad() {  // 并行计算\n        ExecutorService executorService = Executors.newFixedThreadPool(3); // 创建线程池\n        CompletionService completionService = new ExecutorCompletionService(executorService);\n        completionService.submit(super::loadConfigurations, null);      //  耗时 >= 1s\n        completionService.submit(super::loadUsers, null);               //  耗时 >= 2s\n        completionService.submit(super::loadOrders, null);              //  耗时 >= 3s\n\n        int count = 0;\n        while (count < 3) { // 等待三个任务完成\n            if (completionService.poll() != null) {\n                count++;\n            }\n        }\n        executorService.shutdown();\n    }  // 总耗时 max(1s, 2s, 3s)  >= 3s\n\n    public static void main(String[] args) {\n        new ParallelDataLoader().load();\n    }\n\n}\n```\n\n\n\n* 运行结果\n\n```\n[线程 : pool-1-thread-1] loadConfigurations() 耗时 :  1003 毫秒\n[线程 : pool-1-thread-2] loadUsers() 耗时 :  2005 毫秒\n[线程 : pool-1-thread-3] loadOrders() 耗时 :  3005 毫秒\nload() 总耗时：3068 毫秒\n```\n\n\n\n* 结论\n\n明显地，程序改造为并行加载后，性能和资源利用率得到提升，消耗时间取最大者，即三秒，由于线程池操作的消耗，整体时间将略增一点。不过，以上实现为什么不直接使用 `Future#get()`  方法强制所有任务执行完毕，然后再统计总耗时？\n\nReactor 这方面的看法并没有向读者清晰地表达全秒，不过这还不是全部，听听它接下来的说法。\n\n\n\n#### [Reactor](http://projectreactor.io/docs/core/release/reference/#_asynchronicity_to_the_rescue) 认为异步不一定能够救赎\n\n> ### 3.2. Asynchronicity to the Rescue?\n>\n> The second approach (mentioned earlier), seeking more efficiency, can be a solution to the resource wasting problem. By writing *asynchronous*, *non-blocking* code, you let the execution switch to another active task **using the same underlying resources** and later come back to the current process when the asynchronous processing has finished.\n>\n> Java offers two models of asynchronous programming:\n>\n> - **Callbacks**: Asynchronous methods do not have a return value but take an extra `callback` parameter (a lambda or anonymous class) that gets called when the result is available. A well known example is Swing’s `EventListener`hierarchy.\n> - **Futures**: Asynchronous methods return a `Future<T>` **immediately**. The asynchronous process computes a `T` value, but the `Future` object wraps access to it. The value is not immediately available, and the object can be polled until the value is available. For instance, `ExecutorService` running `Callable<T>` tasks use `Future` objects.\n>\n> Are these techniques good enough? Not for every use case, and both approaches have limitations.\n>\n> Callbacks are hard to compose together, quickly leading to code that is difficult to read and maintain (known as \"Callback Hell\").\n>\n> Futures are a bit better than callbacks, but they still do not do well at composition, despite the improvements brought in Java 8 by `CompletableFuture`. \n\n\n\n再次将以上观点归纳，它认为：\n\n- Callbacks 是解决非阻塞的方案，然而他们之间很难组合，并且快速地将代码引导至 \"Callback Hell\" 的不归路\n- Futures  相对于 Callbacks 好一点，不过还是无法组合，不过  `CompletableFuture` 能够提升这方面的不足\n\n\n\n以上 Reactor 的观点仅给出了结论，没有解释现象，其中场景设定也不再简单直白，从某种程度上，这也侧面地说明，Reactive Programming 实际上是”高端玩家“的游戏。接下来，本文仍通过示例的方式，试图解释\"Callback Hell\" 问题以及 `Future` 的限制。\n\n\n\n##### 理解 \"Callback Hell\"\n\n\n\n- Java GUI 示例\n\n```java\npublic class JavaGUI {\n\n    public static void main(String[] args) {\n        JFrame jFrame = new JFrame(\"GUI 示例\");\n        jFrame.setBounds(500, 300, 400, 300);\n        LayoutManager layoutManager = new BorderLayout(400, 300);\n        jFrame.setLayout(layoutManager);\n        jFrame.addMouseListener(new MouseAdapter() { // callback 1\n            @Override\n            public void mouseClicked(MouseEvent e) {\n                System.out.printf(\"[线程 : %s] 鼠标点击，坐标(X : %d, Y : %d)\\n\",\n                        currentThreadName(), e.getX(), e.getY());\n            }\n        });\n        jFrame.addWindowListener(new WindowAdapter() {  // callback 2\n            @Override\n            public void windowClosing(WindowEvent e) {\n                System.out.printf(\"[线程 : %s] 清除 jFrame... \\n\", currentThreadName());\n                jFrame.dispose(); // 清除 jFrame\n            }\n\n            @Override\n            public void windowClosed(WindowEvent e) {\n                System.out.printf(\"[线程 : %s] 退出程序... \\n\", currentThreadName());\n                System.exit(0); // 退出程序\n            }\n        });\n        System.out.println(\"当前线程：\" + currentThreadName());\n        jFrame.setVisible(true);\n    }\n\n    private static String currentThreadName() { // 当前线程名称\n        return Thread.currentThread().getName();\n    }\n}\n```\n\n\n\n* 运行结果\n\n点击窗体并关闭窗口，控制台输出如下：\n\n```\n当前线程：main\n[线程 : AWT-EventQueue-0] 鼠标点击，坐标(X : 180, Y : 121)\n[线程 : AWT-EventQueue-0] 鼠标点击，坐标(X : 180, Y : 122)\n[线程 : AWT-EventQueue-0] 鼠标点击，坐标(X : 180, Y : 122)\n[线程 : AWT-EventQueue-0] 鼠标点击，坐标(X : 180, Y : 122)\n[线程 : AWT-EventQueue-0] 鼠标点击，坐标(X : 180, Y : 122)\n[线程 : AWT-EventQueue-0] 鼠标点击，坐标(X : 201, Y : 102)\n[线程 : AWT-EventQueue-0] 清除 jFrame... \n[线程 : AWT-EventQueue-0] 退出程序...\n```\n\n\n\n* 结论\n\nJava GUI 以及事件/监听模式基本采用匿名内置类实现，即回调实现。从本例可以得出，鼠标的点击确实没有被其他线程给阻塞。不过当监听的维度增多时，Callback 实现也随之增多。Java Swing 事件/监听是一种典型的既符合异步非阻塞，又属于 Callback 实现的场景，其并发模型可为同步或异步。不过，在 Java 8 之前，由于接口无法支持 `default` 方法，当接口方法过多时，通常采用 `Adapter` 模式作为缓冲方案，达到按需实现的目的。尤其在 Java GUI 场景中。即使将应用的 Java 版本升级到 8 ，由于这些 Adapter ”遗老遗少“实现的存在，使得开发人员仍不得不面对大量而繁琐的 Callback 折中方案。既然 Reactor 提出了这个问题，那么它或者 Reactive 能否解决这个问题呢？暂时存疑，下一步是如何理解 `Future` 的限制。\n\n\n\n#####  理解 `Future` 的限制\n\nReactor 的观点仅罗列 `Future` 的一些限制，并没有将它们解释清楚，接下来用两个例子来说明其中原委。\n\n\n\n###### 限制一：`Future` 的阻塞性\n\n在前文示例中，`ParallelDataLoader` 利用 `CompletionService` API 实现 `load*()` 方法的并行加载，如果将其调整为 `Future` 的实现，可能的实现如下：\n\n* Java `Future` 阻塞式加载示例\n\n```java\npublic class FutureBlockingDataLoader extends DataLoader {\n\n    protected void doLoad() {\n        ExecutorService executorService = Executors.newFixedThreadPool(3); // 创建线程池\n        runCompletely(executorService.submit(super::loadConfigurations));  //  耗时 >= 1s\n        runCompletely(executorService.submit(super::loadUsers));           //  耗时 >= 2s\n        runCompletely(executorService.submit(super::loadOrders));          //  耗时 >= 3s\n        executorService.shutdown();\n    } // 总耗时 sum(>= 1s, >= 2s, >= 3s)  >= 6s\n\n    private void runCompletely(Future<?> future) {\n        try {\n            future.get(); // 阻塞等待结果执行\n        } catch (Exception e) {\n        }\n    }\n\n    public static void main(String[] args) {\n        new FutureBlockingDataLoader().load();\n    }\n\n}\n```\n\n\n\n* 运行结果\n\n```\n[线程 : pool-1-thread-1] loadConfigurations() 耗时 :  1003 毫秒\n[线程 : pool-1-thread-2] loadUsers() 耗时 :  2004 毫秒\n[线程 : pool-1-thread-3] loadOrders() 耗时 :  3002 毫秒\nload() 总耗时：6100 毫秒\n```\n\n\n\n*  结论\n\n`ParallelDataLoader` 加载耗时为”3068 毫秒“，调整后的 `FutureBlockingDataLoader` 则比串行的 `DataLoader`  加载耗时（“6031 毫秒”）还要长。说明`Future#get()` 方法不得不等待任务执行完成，换言之，如果多个任务提交后，返回的多个 Future 逐一调用 `get()` 方法时，将会依次 blocking，任务的执行从并行变为串行。这也是之前为什么 `ParallelDataLoader` 不采取 `Future` 的解决方案的原因。\n\n\n\n###### 限制二：`Future`  不支持链式操作\n\n由于 `Future`  无法实现异步执行结果链式处理，尽管 `FutureBlockingDataLoader` 能够解决方法数据依赖以及顺序执行的问题，不过它将并行执行带回了阻塞（串行）执行。所以，它不是一个理想实现。不过  `CompletableFuture`  可以帮助提升 `Future`  的限制：\n\n\n\n* Java `CompletableFuture` 重构 `Future` 链式实现\n\n```java\npublic class FutureChainDataLoader extends DataLoader {\n\n    protected void doLoad() {\n        CompletableFuture\n                .runAsync(super::loadConfigurations)\n                .thenRun(super::loadUsers)\n                .thenRun(super::loadOrders)\n                .whenComplete((result, throwable) -> { // 完成时回调\n                    System.out.println(\"加载完成\");\n                })\n                .join(); // 等待完成\n    }\n\n    public static void main(String[] args) {\n        new ChainDataLoader().load();\n    }\n}\n```\n\n\n\n* 运行结果\n\n```\n[线程 : ForkJoinPool.commonPool-worker-1] loadConfigurations() 耗时 :  1000 毫秒\n[线程 : ForkJoinPool.commonPool-worker-1] loadUsers() 耗时 :  2005 毫秒\n[线程 : ForkJoinPool.commonPool-worker-1] loadOrders() 耗时 :  3001 毫秒\n加载完成\nload() 总耗时：6093 毫秒\n```\n\n\n\n* 结论\n\n通过输出日志分析，  `FutureChainDataLoader` 并没有像 `FutureBlockingDataLoader` 那样使用三个线程分别执行加载任务，仅使用了一个线程，换言之，这三次加载同一线程完成，并且异步于 main 线程，如下所示：\n\n![FutureChainDataLoader](/img/assets/FutureChainDataLoader_Sequences.png)\n\n\n\n尽管 `CompletableFuture`  不仅是异步非阻塞操作，而且还能将 Callback 组合执行，也不存在所谓的 ”Callback Hell“ 等问题。如果强制等待结束的话，又回到了阻塞编程的方式。同时，相对于  `FutureBlockingDataLoader`  实现，重构后的 `FutureChainDataLoader` 不存在明显性能提升。\n\n> 稍作解释，`CompletableFuture`  不仅可以支持 `Future` 链式操作，而且提供三种生命周期回调，即执行回调（Action）、完成时回调（Complete）、和异常回调（Exception），类似于 Spring 4 `ListenableFuture` 以及 Guava `ListenableFuture`。\n\n\n\n至此，Reactor 的官方参考文档再没有出现其他有关”传统编程模型中的某些困境“的描述，或许读者老爷和我一样，对 Reactive 充满疑惑，它真能解决以上问题吗？当然，监听则明，偏听则暗，下面我们再来参考 [Reactive Streams JVM](https://github.com/reactive-streams/reactive-streams-jvm) 的观点。\n\n\n\n#### [Reactive Streams JVM](https://github.com/reactive-streams/reactive-streams-jvm#goals-design-and-scope) 认为异步系统和资源消费需要特殊处理\n\n> Handling streams of data—especially “live” data whose volume is not predetermined—requires special care in an asynchronous system. The most prominent issue is that resource consumption needs to be carefully controlled such that a fast data source does not overwhelm the stream destination. Asynchrony is needed in order to enable the parallel use of computing resources, on collaborating network hosts or multiple CPU cores within a single machine. \n\n观点归纳：\n\n- 流式数据容量难以预判\n- 异步编程复杂\n- 数据源和消费端之间资源消费难以平衡\n\n\n\n此观点与 Reactor 相同的部分是，两者均认为异步编程复杂，而前者还提出了数据结构（流式数据）以及数据消费问题。\n\n\n\n无论两者的观点孰优谁劣，至少说明一个现象，业界对于 Reactive 所解决的问题并非达到一致，几乎各说各话。那么，到底怎样才算 Reactive Programming 呢？\n\n\n\n### 什么是 Reactive Programming\n\n\n\n关于什么是 Reactive Programming，下面给出六种渠道的定义，尝试从不同的角度，了解 Reactive Programming 的意涵。首先了解的是 “[The Reactive Manifesto](https://www.reactivemanifesto.org/)” 中的定义\n\n\n\n#### [The Reactive Manifesto](https://www.reactivemanifesto.org/) 中的定义\n\nReactive Systems are: Responsive, Resilient, Elastic and Message Driven.  \n\n> https://www.reactivemanifesto.org/\n\n\n\n该组织对 Reactive 的定义非常简单，其特点体现在以下关键字：\n\n- 响应的（Responsive）\n- 适应性强的（Resilient）\n- 弹性的（Elastic）\n- 消息驱动的（Message Driven）\n\n\n\n不过这样的定义侧重于 Reactive 系统，或者说是设计 Reactive 系统的原则。\n\n\n\n#### [维基百科](https://en.wikipedia.org/wiki/Reactive_programming)中的定义\n\n维基百科作为全世界的权威知识库，其定义的公允性能够得到保证：\n\n> Reactive programming is a declarative programming paradigm concerned with **data streams** and the **propagation of change**. With this paradigm it is possible to express static (e.g. arrays) or dynamic (e.g. event emitters) data streams with ease, and also communicate that an inferred dependency within the associated execution model exists, which facilitates the automatic propagation of the changed data flow. \n>\n> > 参考地址：https://en.wikipedia.org/wiki/Reactive_programming\n\n维基百科认为 Reactive programming 是一种声明式的编程范式，其核心要素是**数据流（data streams ）**与**其传播变化（ propagation of change）**，前者是关于数据结构的描述，包括静态的数组（arrays）和动态的事件发射器（event emitters）。由此描述，在笔者脑海中浮现出以下技术视图：\n\n- 数据流：Java 8 `Stream` \n- 传播变化：Java `Observable`/`Observer`\n- 事件/监听：Java `EventObject`/`EventListener`\n\n\n\n这些技术能够很好地满足维基百科对于 Reactive 的定义，那么， Reactive 框架和规范的存在意义又在何方？或许以上定义过于抽象，还无法诠释 Reactive 的全貌。于是乎，笔者想到了去 Spring 官方找寻答案，正如所愿，在 Spring Framework 5 官方参考文档中找到其中定义。\n\n\n\n#### [Spring](https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-why-reactive) 5 中的定义\n\n> The term \"reactive\" refers to programming models that are built around **reacting to change** — network component reacting to I/O events, UI controller reacting to mouse events, etc. In that sense **non-blocking** is reactive because instead of being blocked we are now in the mode of reacting to notifications as operations complete or data becomes available. \n>\n> > 参考地址：https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-why-reactive\n\n相对于维基百科的定义，Spring 5 WebFlux 章节同样也提到了变化响应（reacting to change ） ，并且还说明非阻塞（non-blocking）就是 Reactive。同时，其定义的侧重点在响应通知方面，包括操作完成（operations complete）和数据可用（data becomes available）。Spring WebFlux 作为 Reactive Web 框架，天然支持非阻塞，不过早在 Servlet 3.1 规范时代皆以实现以上需求，其中包括 Servlet 3.1 非阻塞 API `ReadListener` 和`WriteListener`，以及 Servlet 3.0 所提供的异步上下文 `AsyncContext` 和事件监听 `AsyncListener`。这些 Servlet 特性正是为 Spring WebFlux 提供适配的以及，所以 Spring WebFlux 能完全兼容 Servlet 3.1 容器。笔者不禁要怀疑，难道 Reactive 仅是新包装的概念吗？或许就此下结论还为时尚早，不妨在了解一下 ReactiveX 的定义。\n\n\n\n#### [ReactiveX](http://reactivex.io/intro.html) 中的定义\n\n广泛使用的 RxJava 作为 ReactiveX 的 Java 实现，对于 Reactive 的定义，ReactiveX 具备相当的权威性：\n\n> ReactiveX extends the observer pattern to support sequences of data and/or events and adds operators that allow you to compose sequences together declaratively while abstracting away concerns about things like low-level threading, synchronization, thread-safety, concurrent data structures, and non-blocking I/O.\n>\n> > 参考地址：http://reactivex.io/intro.html\n\n不过，ReactiveX 并没有直接给 Reactive 下定义，而是通过技术实现手段说明如何实现 Reactive。ReactiveX 作为观察者模式的扩展，通过操作符（Opeators）对数据/事件序列（Sequences of data and/or events )进行操作，并且屏蔽并发细节（abstracting away…），如线程 API（`Exectuor` 、`Future`、`Runnable`）、同步、线程安全、并发数据结构以及非阻塞 I/O。该定义的侧重点主要关注于实现，包括设计模式、数据结构、数据操作以及并发模型。除设计模式之外，Java 8 `Stream` API 具备不少的操作符，包括迭代操作 for-each、map/reduce 以及集合操作 `Collector`等，同时，通过 `parallel()` 和 `sequential()` 方法实现并行和串行操作间的切换，同样屏蔽并发的细节。至于数据结构，`Stream` 和数据流或集合序列可以画上等号。唯独在设计模式上，`Stream` 是迭代器（Iterator）模式实现，而 ReactiveX 则属于观察者（Observer）模式的实现。 对此，Reactor 做了进一步地解释。\n\n\n\n#### [Reactor](http://projectreactor.io/docs/core/release/reference/#intro-reactive) 中的定义\n\n> The reactive programming paradigm is often presented in object-oriented languages as an extension of the Observer design pattern. One can also compare the main reactive streams pattern with the familiar Iterator design pattern, as there is a duality to the Iterable-Iterator pair in all of these libraries. One major difference is that, while an Iterator is pull-based, reactive streams are push-based. \n>\n> > [http](http://projectreactor.io/docs/core/release/reference/)[://projectreactor.io/docs/core/release/reference/#](http://projectreactor.io/docs/core/release/reference/)[intro-reactive](http://projectreactor.io/docs/core/release/reference/)\n\n同样地，Reactor 也提到了观察者模式（Observer pattern ）和迭代器模式（Iterator pattern）。不过它将 Reactive 定义为响应流模式（Reactive streams pattern ），并解释了该模式和迭代器模式在数据读取上的差异，即前者属于推模式（push-based），后者属于拉模式（pull-based）。难道就因为这因素，就要使用 Reactive 吗？这或许有些牵强。个人认为，以上组织均没有坦诚或者简单地向用户表达，都采用一种模糊的描述，多少难免让人觉得故弄玄虚。幸运地是，我从 ReactiveX 官方找到一位前端牛人 [André Staltz](https://gist.github.com/staltz)，他在学习 Reactive 过程中与笔者一样，吃了不少的苦头，在他博文[《The introduction to Reactive Programming you've been missing》](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754)中，他给出了中肯的解释。\n\n\n\n#### [André Staltz](https://gist.github.com/staltz) 给出的定义\n\n> [Reactive programming is programming with **asynchronous data streams**.](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754#reactive-programming-is-programming-with-asynchronous-data-streams)\n>\n> In a way, **this isn't anything new**. Event buses or your typical click events are really an asynchronous event stream, on which you can observe and do some side effects. Reactive is that **idea on steroids**. You are able to create data streams of anything, not just from click and hover events. Streams are cheap and ubiquitous, anything can be a stream: variables, user inputs, properties, caches, data structures, etc. \n>\n> >[\"What is Reactive Programming?\"](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754#what-is-reactive-programming)\n\n他在文章指出，Reactive Programming 并不是新东西，而是司空见惯的混合物，比如事件总监、鼠标点击事件等。同时，文中也提到异步（asynchronous ）以及数据流（data streams）等关键字。如果说因为 Java 8 Stream  是迭代器模式的缘故，它不属于Reactive Programming 范式的话，那么，Java GUI 事件/监听则就是 Reactive。那么，Java 开发人员学习 RxJava、Reactor、或者 Java 9 Flow API 的必要性又在哪里呢？因此，非常有必要深入探讨 Reactive Programming 的使用场景。\n\n\n\n\n\n### Reactive Programming 使用场景\n\n正如同 Reactive Programming 的定义那样，各个组织各执一词，下面仍采用多方引证的方式，寻求 Reactive Programming 使用场景的“最大公约数”。\n\n\n\n#### [Reactive Streams JVM](https://github.com/reactive-streams/reactive-streams-jvm) 认为的使用场景\n\n> The main goal of Reactive Streams is to govern the exchange of stream data across an asynchronous boundary. \n>\n> > https://github.com/reactive-streams/reactive-streams-jvm\n\n[Reactive Streams JVM](https://github.com/reactive-streams/reactive-streams-jvm) 认为 Reactive Streams 用于在异步边界（asynchronous boundary）管理流式数据交换（ govern the exchange of stream data）。异步说明其并发模型，流式数据则体现数据结构，管理则强调它们的它们之间的协调。\n\n\n\n#### [Spring 5](https://docs.spring.io/spring/docs/5.0.7.RELEASE/spring-framework-reference/web-reactive.html#webflux-performance) 认为的使用场景\n\n> Reactive and non-blocking generally do not make applications run faster. They can, in some cases, for example if using the `WebClient` to execute remote calls in parallel. On the whole it requires more work to do things the non-blocking way and that can increase slightly the required processing time. \n>\n> The key expected benefit of reactive and non-blocking is the ability to scale with a small, fixed number of threads and less memory. That makes applications more resilient under load because they scale in a more predictable way. \n\nSpring 认为 Reactive 和非阻塞通常并非让应用运行更快速（generally do not make applications run faster），甚至会增加少量的处理时间，因此，它的使用场景则利用较少的资源，提升应用的伸缩性（scale with a small, fixed number of threads and less memory）。\n\n\n\n#### [ReactiveX](http://reactivex.io/intro.html) 认为的使用场景\n\n> The ReactiveX Observable model allows you to treat streams of asynchronous events with the same sort of simple, composable operations that you use for collections of data items like arrays. It frees you from tangled webs of callbacks, and thereby makes your code more readable and less prone to bugs. \n\nReactiveX 所描述的使用场景与 Spring 的不同，它没有从性能入手，而是代码可读性和减少 Bugs 的角度出发，解释了 Reactive Programming 的价值。同时，强调其框架的核心特性：异步（asynchronous）、同顺序（same sort）和组合操作（composable operations）。它也间接地说明了，Java 8 `Stream` 在组合操作的限制，以及操作符的不足。\n\n\n\n#### [Reactor](http://projectreactor.io/docs/core/release/reference/#intro-reactive) 认为的使用场景\n\n> Composability and readability\n>\n> Data as a flow manipulated with a rich vocabulary of operators\n>\n> Nothing happens until you subscribe\n>\n> Backpressure or the ability for the consumer to signal the producer that the rate of emission is too high\n>\n> High level but high value abstraction that is concurrency-agnostic\n\nReactor 同样强调结构性和可读性（Composability and readability）和高层次并发抽象（High level abstraction），并明确地表示它提供丰富的数据操作符（ rich vocabulary of operators）弥补 `Stream` API 的短板，还支持背压（Backpressure）操作，提供数据生产者和消费者的消息机制，协调它们之间的产销失衡的情况。同时，Reactor 采用订阅式数据消费（Nothing happens until you subscribe）的机制，实现 `Stream` 所不具备的数据推送机制。\n\n\n\n至此，讨论接近尾声，最后的部分将 Reactive Programming 内容加以总结。\n\n\n\n## 总结 Reactive Programming\n\n\n\nReactive Programming 作为观察者模式（[Observer](https://en.wikipedia.org/wiki/Observer_pattern)） 的延伸，不同于传统的命令编程方式（ [Imperative programming](https://en.wikipedia.org/wiki/Imperative_programming)）同步拉取数据的方式，如迭代器模式（[Iterator](https://en.wikipedia.org/wiki/Iterator_pattern)） 。而是采用数据发布者同步或异步地推送到数据流（Data Streams）的方案。当该数据流（Data Steams）订阅者监听到传播变化时，立即作出响应动作。在实现层面上，Reactive Programming 可结合函数式编程简化面向对象语言语法的臃肿性，屏蔽并发实现的复杂细节，提供数据流的有序操作，从而达到提升代码的可读性，以及减少 Bugs 出现的目的。同时，Reactive Programming  结合背压（Backpressure）的技术解决发布端生成数据的速率高于订阅端消费的问题。\n\n\n\n\n\n## 后记\n\n2005年，李敖大师曾在上海复旦大学做过一次演讲，他在讲到“放弃自由主义，注重务实”的部分时，引述一段故事：\n\n> 美国有一个报纸，办报的人叫ABBOTT，他晚年的时候写回忆录，他爸爸是一个写儿童书的作家，他爸爸临死前告诉他说，感觉到人间所有的教会的争执90％都是名词之争。这个小ABBOTT老了以后，他回忆这段话，他说我回忆我爸爸告诉我所有人间宗教的争执90％都是名词之争，他说我发现我爸爸数学不好，原来最后那10％也是名词之争。\n\n实际上，名词之争的战场不限于宗教教会，技术领域不也是如此吗？Reactive Programming 实际是一种技术，却被各自表达。有些定义含糊不清，有些定义则空洞无实，还有一些则是夸大其词，只有少数保持客观中立。不免让然唏嘘技术领域的非理性营销。当了解了 Reactive Programming 的本质后，您的热情还能像如初般地高涨吗？\n\n"
  },
  {
    "path": "_posts/2019-03-05-《Java编程方法论 响应式之Rxjava篇》序.md",
    "content": "# 《Java编程方法论 响应式之Rxjava篇》序\n\n在《2019 一月的InfoQ 架构和设计趋势报告》[^1]中，响应式编程（Reactive Programming）和函数式（Functional Programing）仍旧编列在第一季度（Q1）的 Early Adopters（早期采纳者） 中。尽管这仅是一家之言，然而不少的开发人员逐渐意识到 Reactive 之风俨然吹起。也许您的生产系统尚未出现 Reactive 的身影，不过您可能听说过 Spring WebFlux 或 Netflix Hystrix 等开源框架。笔者曾请教过 Pivotal（Spring 母公司）布道师 Josh Long[^2]：”Spring 技术栈未来的重心是否要布局在 Reactive 之上？“。对方的答复是：”没错，Reactive 是未来趋势。“。同时，越来越多的开源项目开始签署 Reactive 宣言（The Reactive Manifesto）[^3]，并喊出 ”Web Are Reactive“ 的口号。\n\n或许开源界的种种举动无法说服您向 Reactive 的”港湾“中停靠，不过 Java 9  Flow API[^4] 的引入，又给业界注入了一剂强心针。不难看出，无论是 Java API，还是 Java 框架均走向了 Reactive 编程模型的道路，这并非是一种巧合。\n\n通常，人们谈到的 Reactive 可与 Reactive 编程划上等号，以”非阻塞（Non-Blocking）“和”异步（Asynchronous）“的特性并述，数据结构与操作相辅相成。Reactive 涉及函数式和并发两种编程模型，前者关注语法特性，后者强调执行效率。简言之，Reactive 编程的意图在于 ”Less Code，More Efficient“。除此之外，个人认为 Reactive 更大的价值在于统一 Java 并发编程模型，使得同步和异步的实现代码无异，同时做到 Java 编程风格与其他编程语言更好地融合，或许您也发现 Java  与 JS 在 Reactive 方面并不存在本质区别。纵观 Java 在 Reactive 编程上的发展而看，其特性更新可谓是步步为营，如履薄冰。尽管 Java 线程 API `Thread` 与 `Runnable` 就已具备异步以及非阻塞的能力，然而同步和异步编程的模式并不统一，并且理解 `Thread` API 的细节和管理线程生命周期的成本均由开发人员概括承受。虽然 Java 5 引入 J.U.C 框架（Java 并发框架）之后， `ExecutorService` 实现减轻了以上负担。不过开发人员仍需关注 `ExecutorService` 实现细节，比如怎样合理地设置线程池空间以及阻塞队列又成为新的挑战。为此，Java 7 又引入 `ForkJoinPool` API，不过此时的J.U.C 框架与 Reactive 理念仍存在距离，即使是线程安全的数据结构，也并不具备并行计算的能力，如：集合并行排序，同时操作集合的手段也相当的贫瘠，缺少类似 Map/Reduce 等操作。不过这些困难只是暂时的，终究被 Java 8 ”救赎“。`Stream` API 的出现不但具备数据操作在串行和并行间自由切换的能力，如 `sequential()` 以及 `parallel()` 方法，而且淡化了并发的特性，如 `sorted()` 方法即可能是传统排序，亦或是并行排序。相同的设计哲学也体现在 Java Reactive 实现框架中，如同书中提及的 RxJava[^5] API `io.reactivex.Observable` 。统一编程模型只是 `Stream` 其中设计目标之一，它结合 Lambda 语法特性，虽然提供了数量可观的操作方法，如 `flatMap()` 等，然而无论对比 RxJava，还是  Reactor[^6] ，`Stream` 操作方法却又相形见绌。值得一提的是，这些操作方法在 Reactive 的术语中称之为操作符（Operators）。当然框架内建的操作符的多与寡，并非判断其是否为 Reactive 实现的依据。其中决定性因素在于数据必须来源于发布方（生产者）的”推送（Push）“，而非消费端的”拉取（Pull）“。显然，`Stream`  属于消费端已就绪（Ready）的数据集合，并不存在其他数据推送源。不过 JVM 语言早期的 Reactive 定义处于模糊地带，如 RxJava  API 属于观察者模式（Observer Pattern）[^7]的扩展，而非迭代器（Iterator Pattern）模式[^8]的实现。而 Reactor 的实现则拥抱 Reactive Streams 规范[^9] ，该规范消费端对于数据的操作是被动的处理，而非主动的索。换言之，数据的到达存在着不确定性[^10]。当推送的数据无法得到消费端及时效应时，Reactive 框架必须提供背压（Backpressure）[^11]实现，确保消费端拥有”拒绝的权利（cancel）”。在此理论基础上，Reactive Streams 规范定义了一套抽象的 API，作为 Java 9 `java.util.concurrent.Flow` API 的顶层设计。不过关于操作符的部分，该规范似乎不太关心，这也是为什么 RxJava 和 Reactor 均称自身为 Reactive 扩展框架的原因，同时两者在 API 级别提供多种调度器（Schedulers）[^12]实现，适配不同并发场景提供。尽管 Reactive 定义在不同的阵营之间存在差异，援引本人在《Reactive-Programming-一种技术-各自表述》[^13]文中的总结：\n\n> Reactive Programming 作为观察者模式（[Observer](https://en.wikipedia.org/wiki/Observer_pattern)） 的延伸，不同于传统的命令编程方式（ [Imperative programming](https://en.wikipedia.org/wiki/Imperative_programming)）同步拉取数据的方式，如迭代器模式（[Iterator](https://en.wikipedia.org/wiki/Iterator_pattern)） 。而是采用数据发布者同步或异步地推送到数据流（Data Streams）的方案。当该数据流（Data Steams）订阅者监听到传播变化时，立即作出响应动作。在实现层面上，Reactive Programming 可结合函数式编程简化面向对象语言语法的臃肿性，屏蔽并发实现的复杂细节，提供数据流的有序操作，从而达到提升代码的可读性，以及减少 Bugs 出现的目的。同时，Reactive Programming 结合背压（Backpressure）的技术解决发布端生成数据的速率高于订阅端消费的问题。\n\n不难看出，Reactive 是一门综合的编程艺术，在实现框架的加持下，相同的代码逻辑实现同步和异步非阻塞功能，从而达到提升应用整体性能的目的。不过现实的情况或许没有那么理想，Spring 官方文档在《Web on Reactive Stack》章节中提到，\"Reactive 和非阻塞通常并不是让应用运行的更快\"[^14]：\n\n> *Reactive and non-blocking generally do not make applications run faster.*\n\n为此，JHipster[^15] 给出了一份《 Spring 5 WebFlux 性能测试报告》[^16]，其中一条结论是，”Reactive 应用并没有表现出速度提升（甚至是变得更差）“：\n\n> *No improvement in speed was observed with our reactive apps (the Gatling results are even slightly worse).*\n\n数月后，看似相反的结论却在DZone[^17]一篇名为《Raw Performance Numbers - Spring Boot 2 Webflux vs. Spring Boot 1》[^18]的文中出现，测试结果是 Spring Boot 2 WebFlux在高并发下响应时间更为平稳。实际上，这个测试结论有些”关公战秦琼“的味道，毕竟 Spring Boot 2.0 下的 WebFlux 和 Spring Boot 1.0 中的 Servlet  容器所使用线程模型是不同的，并且 Servlet 3.0 异步以及非阻塞特性缺省是关闭的。不过以上两篇的结论并不矛盾，前者关注于响应速度，后者则强调吞吐量，都是性能的核心指标。遗憾的是，两篇文章均未对各自的测试用例做出调优，因此以上结论都存在一定的局限性，这也是本人对 Reactive 技术能否提升性能提出质疑的地方。\n\n如果本人是国内提出 Reactive 问题的第一人的话，那么知秋[^19]就是国内第一个解决问题的人。作为国内为数不多的 Reactive 以及 NIO 方面的专家，在技术研究上，他追求格物致知，不轻忽技术细节。在知识分享上，他可谓是知无不言，言无不尽，不仅在社交群中答疑解惑，而且录制免费视频，发布在 B 站[^20]以及 YouTube 频道[^21]，并得到 Josh Long 等大佬的推文(Twitter)。或许以上方式还不足以完整地讨论 Java Reactive 技术，知秋选择了漫长而又艰苦的著书之路，尽管他是本人的朋友，然而 ”内举不避亲“，笔者推荐给读者朋友，首先是因为这是大陆地区第一本全面解读 Java Reactive 技术的书籍，除作者的雄厚技术积累背书之外，书中的知识脉络是循序渐进的。同时，这也是一本引人深思的书，本书在导读源码的同时，也引导读者对于代码设计上的思考。再者，这又是一本知识苦旅的书，因为它涉及面较广，读者不仅需要具备一定的 Java 并发以及面向对象设计，而且需要读者付出较多的时间去反复推敲。正所谓”夫夷以近，则游者众；险以远，则至者少“[^22]，笔者希望读者在购买此书后，不轻言放弃，当您面临挑战时，那才是成长的开始。同时，也期盼读者将 Reactive 技术付之于实践，提早触碰未来。\n\n\n\n小马哥（mercyblitz)[^23]\n\n2019 年 3 月 5 日\n\n[^1]: Architecture and Design InfoQ Trends Report - January 2019 - https://www.infoq.com/articles/architecture-trends-2019\n[^2]: Josh (@starbuxman) is the Spring Developer Advocate at Pivotal and a Java Champion - https://spring.io/team/jlong\n[^3]: The Reactive Manifesto -  https://www.reactivemanifesto.org\n[^4]:  https://docs.oracle.com/javase/9/docs/api/java/util/concurrent/Flow.html\n[^5]: RxJava: Reactive Extensions for the JVM - https://github.com/ReactiveX/RxJava6\n[^6]:  Reactor is a fully non-blocking reactive programming foundation for the JVM, with efficient demand management (in the form of managing \"backpressure\"). - https://github.com/reactor/reactor-core\n[^7]: Observer Pattern - http://en.wikipedia.org/wiki/observer_pattern\n[^8]: Iterator Pattern - https://en.wikipedia.org/wiki/Iterator_pattern\n[^9]: Reactive Streams is an initiative to provide a standard for asynchronous stream processing with non-blocking back pressure. - https://www.reactive-streams.org/\n[^10]:  Handling streams of data—especially “live” data whose volume is not predetermined - https://github.com/reactive-streams/reactive-streams-jvm#goals-design-and-scope\n[^11]: Backpressure is an integral part of this model in order to allow the queues which mediate between threads to be bounded. The benefits of asynchronous processing would be negated if the backpressure signals were synchronous (see also the [Reactive Manifesto](http://reactivemanifesto.org/)), therefore care has been taken to mandate fully non-blocking and asynchronous behavior of all aspects of a Reactive Streams implementation. - https://github.com/reactive-streams/reactive-streams-jvm#goals-design-and-scope\n[^12]: RxJava Scheduler - http://reactivex.io/documentation/scheduler.html, Reactor Schedulers : https://projectreactor.io/docs/core/release/reference/#schedulers\n[^13]: 《Reactive Programming 一种技术，各自表述》：https://mercyblitz.github.io/2018/07/25/Reactive-Programming-一种技术-各自表述\n[^14]:  1.1.6. Performance - https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-performance\n[^15]:JHipster -https://www.jhipster.tech/\n[^16]: 《Spring 5 WebFlux: Performance tests》- https://blog.ippon.tech/spring-5-webflux-performance-tests\n[^17]:  DZone : https://dzone.com\n[^18]:《Raw Performance Numbers - Spring Boot 2 Webflux vs. Spring Boot 1》 - https://dzone.com/articles/raw-performance-numbers-spring-boot-2-webflux-vs-s\n[^19]:  知秋，本书的作者笔名\n[^20]: https://space.bilibili.com/2494318\n[^21]: https://www.youtube.com/channel/UCmxVT2rkFwDX1tPpVDPJQaQ\n[^22]:《游褒禅山记》，王安石\n[^23]:小马哥，[Java 劝退师](https://www.douyu.com/mercyblitz)，[Apache Dubbo](https://dubbo.apache.org/) PPMC、[Spring Cloud Alibaba](https://github.com/spring-cloud-incubator/spring-cloud-alibaba) 项目架构师 - https://mercyblitz.github.io/about/"
  },
  {
    "path": "_posts/2019-04-26-Dubbo Spring Cloud 重塑微服务治理.md",
    "content": "> 原文链接：[Dubbo Spring Cloud 重塑微服务治理](https://mp.weixin.qq.com/s/K60e1VkAWjwQXftmHw74NQ)，来自于微信公众号：`次灵均阁`\n\n## 摘要\n\n在 Java 微服务生态中，Spring Cloud[^1] 成为了开发人员的首选技术栈，然而随着实践的深入和运用规模的扩大，大家逐渐意识到 Spring Cloud 的局限性。在服务治理方面，相较于 Dubbo[^2] 而言，Spring Cloud 并不成熟。遗憾的是，Dubbo 往往被部分开发者片面地视作服务治理的 RPC 框架，而非微服务基础设施。即使是那些有意将 Spring Cloud 迁移至 Dubbo 的小伙伴，当面对其中迁移和改造的成本时，难免望而却步。庆幸的是，Dubbo 生态体系已发生巨大变化，Dubbo Spring Cloud 作为 Spring Cloud Alibaba[^3] 的最核心组件，完全地拥抱 Spring Cloud 技术栈，不但无缝地整合 Spring Cloud 注册中心，包括 Nacos[^4]、Eureka[^5]、Zookeeper[^6] 以及 Consul[^7]，而且完全地兼容 Spring Cloud Open Feign[^8] 以及 @LoadBalanced RestTemplate，本文将讨论 Dubbo Spring Cloud 对 Spring Cloud 技术栈所带来的革命性变化。\n\n> 注：由于 Spring Cloud 技术栈涵盖的特性众多，因此本文讨论的范围仅限于服务治理部分。\n\n\n\n## 简介\n\nDubbo Spring Cloud 基于 Dubbo Spring Boot 2.7.1[^9] 和 Spring Cloud 2.x 开发，无论开发人员是 Dubbo 用户还是 Spring Cloud 用户，都能轻松地驾驭，并以接近“零”成本的代价使应用向上迁移。Dubbo Spring Cloud 致力于简化 Cloud Native 开发成本，提高研发效能以及提升应用性能等目的。\n\n2019年4月19日，Dubbo Spring Cloud 首个 Preview Release，随同 Spring Cloud Alibaba `0.2.2.RELEASE` 和  `0.9.0.RELEASE` 一同发布[^10]，分别对应 Spring Cloud Finchley[^11] 与 Greenwich[^12] (下文分别简称为 “F” 版 和 “G” 版) 。\n\n\n\n## 版本支持\n\n由于 Spring 官方宣布 Spring Cloud Edgware(下文简称为 “E” 版) 将在 2019 年 8 月 1 号后停止维护13，因此，目前 Dubbo Spring Cloud 发布版本并未对 “E” 版提供支持，仅为 “F” 版 和 “G” 版开发，同时也建议和鼓励 Spring Cloud 用户更新至 “F” 版 或 “G” 版。\n\n同时，Dubbo Spring Cloud 基于 Apache Dubbo Spring Boot 2.7.1 开发（最低 Java 版本为 1.8），提供完整的 Dubbo 注解驱动、外部化配置以及 Production-Ready 的特性，详情请参考：https://github.com/apache/incubator-dubbo-spring-boot-project\n\n以下表格将说明 Dubbo Spring Cloud 版本关系映射关系：\n\n| Spring Cloud | Spring Cloud Alibaba | Spring Boot | Dubbo Spring Boot                  |\n| ------------ | -------------------- | ----------- | ---------------------------------- |\n| Finchley     | `0.2.2.RELEASE`      | `2.0.x`     | `2.7.1`                            |\n| Greenwich    | `0.9.0.RELEASE`      | `2.1.x`     | `2.7.1`                            |\n| Edgware      | `0.1.2.RELEASE`      | `1.5.x`     | :x: Dubbo Spring Cloud 不支持该版本 |\n\n\n\n## 功能特性\n\n由于 Dubbo Spring Cloud 构建在原生的 Spring Cloud 之上，其服务治理方面的能力可认为是 Spring Cloud Plus，不仅完全覆盖 Spring Cloud 原生特性[^13]，而且提供更为稳定和成熟的实现，特性比对如下表所示：\n\n| 功能组件                                             | Spring Cloud                           | Dubbo Spring Cloud                                     |\n| ---------------------------------------------------- | -------------------------------------- | ------------------------------------------------------ |\n| 分布式配置（Distributed configuration）              | Git、Zookeeper、Consul、JDBC           | Spring Cloud 分布式配置 + Dubbo 配置中心[^14]          |\n| 服务注册与发现（Service registration and discovery） | Eureka、Zookeeper、Consul              | Spring Cloud 原生注册中心[^15] + Dubbo 原生注册中心[^16] |\n| 负载均衡（Load balancing）                           | Ribbon（随机、轮询等算法）             | Dubbo 内建实现（随机、轮询等算法 + 权重等特性）        |\n| 服务熔断（Circuit Breakers）                         | Spring Cloud Hystrix                   | Spring Cloud Hystrix + Alibaba Sentinel[^17] 等        |\n| 服务调用（Service-to-service calls）                 | Open Feign、`RestTemplate`             | Spring Cloud 服务调用 + Dubbo `@Reference`             |\n| 链路跟踪（Tracing）                                  | Spring Cloud Sleuth[^18] + Zipkin[^19] | Zipkin、opentracing 等                                 |\n\n\n\n### 高亮特性\n\n#### 1. Dubbo 使用 Spring Cloud 服务注册与发现\n\nDubbo Spring Cloud 基于 Spring Cloud Commons 抽象实现 Dubbo 服务注册与发现，应用只需增添外部化配置属性 “`dubbo.registry.address = spring-cloud://localhost`”，就能轻松地桥接到所有原生 Spring Cloud 注册中心，包括：\n- Nacos\n- Eureka\n- Zookeeper\n- Consul\n\n> Dubbo Spring Cloud 将在下个版本支持 Spring Cloud 注册中心与 Dubbo 注册中心并存，提供双注册机制，实现无缝迁移。\n\n\n\n#### 2. Dubbo 作为 Spring Cloud 服务调用\n\n默认情况，Spring Cloud Open Feign 以及 `@LoadBalanced` `RestTemplate` 作为 Spring Cloud 的两种服务调用方式。Dubbo Spring Cloud 为其提供了第三种选择，即 Dubbo 服务将作为 Spring Cloud 服务调用的同等公民出现，应用可通过 Apache Dubbo 注解 `@Service`和 `@Reference` 暴露和引用 Dubbo 服务，实现服务间多种协议的通讯。同时，也可以利用 Dubbo 泛化接口轻松实现服务网关。\n\n\n\n#### 3. Dubbo 服务自省\n\nDubbo Spring Cloud 引入了全新的服务治理特性 - 服务自省（Service Introspection），其设计目的在于最大化减轻注册中心负载，去 Dubbo 注册元信息中心化。假设一个 Spring Cloud 应用引入 Dubbo Spring Boot Starter，并暴露 N 个 Dubbo 服务，以 [Dubbo Nacos 注册中心](https://github.com/apache/incubator-dubbo/tree/master/dubbo-registry/dubbo-registry-nacos) 为例，当前应用将注册 N+1 个 Nacos 应用，除 Spring Cloud 应用本身之前，其余 N 个应用均来自于 Dubbo 服务，当 N 越大时，注册中心负载越重。因此，Dubbo Spring Cloud 应用对注册中心的负载相当于传统 Dubbo 的 N 分之一，在不增加基础设施投入的前提下，理论上，使其集群规模扩大 N 倍。当然，未来的 Dubbo 也将提供服务自省的能力。\n\n\n\n#### 4. Dubbo 迁移 Spring Cloud 服务调用\n\n尽管 Dubbo Spring Cloud 完全地保留了原生 Spring Cloud 服务调用特性，不过 Dubbo 服务治理的能力是 Spring Cloud Open Feign 所不及的，如高性能、高可用以及负载均衡稳定性等方面。因此，建议开发人员将 Spring Cloud Open Feign 或者 `@LoadBalanced` `RestTemplate` 迁移为 Dubbo 服务。考虑到迁移过程并非一蹴而就，因此，Dubbo Spring Cloud 提供了方案，即 `@DubboTransported` 注解。该注解能够帮助服务消费端的 Spring Cloud Open Feign 接口以及 `@LoadBalanced` `RestTemplate` Bean 底层走 Dubbo 调用（可切换 Dubbo 支持的协议），而服务提供方则只需在原有 `@RestController` 类上追加 Dubbo `@Servce` 注解（需要抽取接口）即可，换言之，在不调整 Feign 接口以及 `RestTemplate` URL 的前提下，实现无缝迁移。如果迁移时间充分的话，建议使用 Dubbo 服务重构系统中的原生 Spring Cloud 服务的定义。\n\n\n\n## 简单示例\n\n开发 Dubbo Spring Cloud 应用的方法与传统 Dubbo 或 Spring Cloud 应用类似，按照以下步骤就能完整地实现Dubbo 服务提供方和消费方的应用，完整的示例代码请访问一下资源：\n\n- Dubbo 服务提供方应用 - <https://github.com/spring-cloud-incubator/spring-cloud-alibaba/tree/master/spring-cloud-alibaba-examples/spring-cloud-alibaba-dubbo-examples/spring-cloud-dubbo-server-sample>\n- Dubbo 服务消费方应用 - <https://github.com/spring-cloud-incubator/spring-cloud-alibaba/tree/master/spring-cloud-alibaba-examples/spring-cloud-alibaba-dubbo-examples/spring-cloud-dubbo-client-sample>\n\n\n\n### 定义 Dubbo 服务接口\n\nDubbo 服务接口是服务提供方与消费方的远程通讯契约，通常由普通的 Java 接口（interface）来声明，如 `EchoService` 接口：\n\n```java\npublic interface EchoService {\n\n    String echo(String message);\n}\n```\n\n为了确保契约的一致性，推荐的做法是将 Dubbo 服务接口打包在第二方或者第三方的 artifact（jar）中，如以上接口就存放在 artifact [spring-cloud-dubbo-sample-api](https://github.com/spring-cloud-incubator/spring-cloud-alibaba/tree/master/spring-cloud-alibaba-examples/spring-cloud-alibaba-dubbo-examples/spring-cloud-dubbo-sample-api) 之中。\n\n对于服务提供方而言，不仅通过依赖 artifact 的形式引入 Dubbo 服务接口，而且需要将其实现。对应的服务消费端，同样地需要依赖该 artifact，并以接口调用的方式执行远程方法。接下来进一步讨论怎样实现 Dubbo 服务提供方和消费方。\n\n\n\n### 实现 Dubbo 服务提供方\n\n#### 初始化 `spring-cloud-dubbo-server-sample` Maven 工程\n\n首先，创建 `artifactId` 名为 `spring-cloud-dubbo-server-sample` 的 Maven 工程，并在其  `pom.xml` 文件中增添 \nDubbo Spring Cloud 必要的依赖：\n\n```xml\n<dependencies>\n    <!-- Sample API -->\n    <dependency>\n        <groupId>org.springframework.cloud</groupId>\n        <artifactId>spring-cloud-dubbo-sample-api</artifactId>\n        <version>${project.version}</version>\n    </dependency>\n\n    <!-- Spring Boot dependencies -->\n    <dependency>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-actuator</artifactId>\n    </dependency>\n\n    <!-- Dubbo Spring Cloud Starter -->\n    <dependency>\n        <groupId>org.springframework.cloud</groupId>\n        <artifactId>spring-cloud-starter-dubbo</artifactId>\n    </dependency>\n\n    <!-- Spring Cloud Nacos Service Discovery -->\n    <dependency>\n        <groupId>org.springframework.cloud</groupId>\n        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>\n    </dependency>\n</dependencies>\n```\n\n以上依赖 artifact 说明如下：\n\n- `spring-cloud-dubbo-sample-api` : 提供 `EchoService` 接口的 artifact\n- `spring-boot-actuator` : Spring Boot Production-Ready artifact，间接引入 `spring-boot` artifact\n- `spring-cloud-starter-dubbo` : Dubbo Spring Cloud Starter `artifact`，间接引入 `dubbo-spring-boot-starter` 等 artifact\n- `spring-cloud-starter-alibaba-nacos-discovery` : Nacos Spring Cloud 服务注册与发现 `artifact`\n\n\n值得注意的是，以上 artifact 未指定版本(version)，因此，还需显示地声明 `<dependencyManagement>` :\n\n```xml\n<dependencyManagement>\n    <dependencies>\n        <!-- Spring Cloud Alibaba dependencies -->\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-alibaba-dependencies</artifactId>\n            <version>0.9.0.RELEASE</version>\n            <type>pom</type>\n            <scope>import</scope>\n        </dependency>\n    </dependencies>\n</dependencyManagement>\n```\n\n> 以上完整的 Maven 依赖配置，请参考 `spring-cloud-dubbo-server-sample` [`pom.xml`](spring-cloud-dubbo-server-sample/pom.xml) 文件\n\n完成以上步骤之后，下一步则是实现 Dubbo 服务\n\n\n#### 实现 Dubbo 服务\n\n`EchoService` 作为暴露的 Dubbo 服务接口，服务提供方 `spring-cloud-dubbo-server-sample` 需要将其实现：\n\n```java\n@org.apache.dubbo.config.annotation.Service\nclass EchoServiceImpl implements EchoService {\n\n    @Override\n    public String echo(String message) {\n        return \"[echo] Hello, \" + message;\n    }\n}\n```\n\n其中，`@org.apache.dubbo.config.annotation.Service` 是 Dubbo 服务注解，仅声明该 Java 服务（本地）实现为 Dubbo 服务。\n因此，下一步需要将其配置 Dubbo 服务（远程）。\n\n\n\n#### 配置 Dubbo 服务提供方\n\n在暴露 Dubbo 服务方面，推荐开发人员外部化配置的方式，即指定 Java 服务实现类的扫描基准包。\n> Dubbo Spring Cloud 继承了 Dubbo Spring Boot 的外部化配置特性，也可以通过标注 `@DubboComponentScan` 来实现基准包扫描。\n\n同时，Dubbo 远程服务需要暴露网络端口，并设定通讯协议，完整的 YAML 配置如下所示：\n\n```yaml\ndubbo:\n  scan:\n    # dubbo 服务扫描基准包\n    base-packages: org.springframework.cloud.alibaba.dubbo.bootstrap\n  protocol:\n    # dubbo 协议\n    name: dubbo\n    # dubbo 协议端口（ -1 表示自增端口，从 20880 开始）\n    port: -1\n  registry:\n    # 挂载到 Spring Cloud 注册中心\n    address: spring-cloud://localhost\n    \nspring:\n  application:\n    # Dubbo 应用名称\n    name: spring-cloud-alibaba-dubbo-server\n  main:\n    # Spring Boot 2.1 需要设定\n    allow-bean-definition-overriding: true\n  cloud:\n    nacos:\n      # Nacos 服务发现与注册配置\n      discovery:\n        server-addr: 127.0.0.1:8848\n```\n\n以上 YAML 内容，上半部分为 Dubbo 的配置：\n\n- `dubbo.scan.base-packages` : 指定 Dubbo 服务实现类的扫描基准包\n- `dubbo.protocol` : Dubbo 服务暴露的协议配置，其中子属性 `name` 为协议名称，`port` 为协议端口（ -1 表示自增端口，从 20880 开始）\n- `dubbo.registry` : Dubbo 服务注册中心配置，其中子属性 `address` 的值 \"spring-cloud://localhost\"，说明挂载到 Spring Cloud 注册中心\n> 当前 Dubbo Spring Cloud 实现必须配置 `dubbo.registry.address = spring-cloud://localhost`，下一个版本将其配置变为可选\n> （参考 [issue #592](https://github.com/spring-cloud-incubator/spring-cloud-alibaba/issues/592)），\n> 并且支持传统 Dubbo 协议的支持（参考 [issue #588](https://github.com/spring-cloud-incubator/spring-cloud-alibaba/issues/588)）\n\n下半部分则是 Spring Cloud 相关配置：\n\n- `spring.application.name` : Spring 应用名称，用于 Spring Cloud 服务注册和发现。\n> 该值在 Dubbo Spring Cloud 加持下被视作 `dubbo.application.name`，因此，无需再显示地配置 `dubbo.application.name`\n- `spring.main.allow-bean-definition-overriding` : 在 Spring Boot 2.1 以及更高的版本增加该设定，\n因为 Spring Boot 默认调整了 Bean 定义覆盖行为。（推荐一个好的 Dubbo 讨论 [issue #3193](https://github.com/apache/incubator-dubbo/issues/3193#issuecomment-474340165)）\n- `spring.cloud.nacos.discovery` : Nacos 服务发现与注册配置，其中子属性 server-addr 指定 Nacos 服务器主机和端口\n\n> 以上完整的 YAML 配置文件，请参考 `spring-cloud-dubbo-server-sample` [`bootstrap.yaml`](spring-cloud-dubbo-server-sample/src/main/resources/bootstrap.yaml) 文件\n\n\n完成以上步骤后，还需编写一个 Dubbo Spring Cloud 引导类。\n\n\n#### 引导 Dubbo Spring Cloud 服务提供方应用\n\nDubbo Spring Cloud 引导类与普通 Spring Cloud 应用并无差别，如下所示：\n```java\n@EnableDiscoveryClient\n@EnableAutoConfiguration\npublic class DubboSpringCloudServerBootstrap {\n\n    public static void main(String[] args) {\n        SpringApplication.run(DubboSpringCloudServerBootstrap.class);\n    }\n}\n```\n\n在引导 `DubboSpringCloudServerBootstrap` 之前，请提前启动 Nacos 服务器。\n当 `DubboSpringCloudServerBootstrap` 启动后，将应用 `spring-cloud-dubbo-server-sample` 将出现在 Nacos 控制台界面。\n\n\n当 Dubbo 服务提供方启动后，下一步实现一个 Dubbo 服务消费方。\n\n\n\n### 实现 Dubbo 服务消费方\n\n由于 Java 服务就 `EchoService`、服务提供方应用 `spring-cloud-dubbo-server-sample` 以及 Nacos 服务器均已准备完毕。Dubbo 服务消费方\n只需初始化服务消费方 Maven 工程 `spring-cloud-dubbo-client-sample` 以及消费 Dubbo 服务。\n\n\n\n#### 初始化 `spring-cloud-dubbo-client-sample` Maven 工程\n\n与服务提供方 Maven 工程类，需添加相关 Maven 依赖：\n\n```xml\n<dependencyManagement>\n    <dependencies>\n        <!-- Spring Cloud Alibaba dependencies -->\n        <dependency>\n            <groupId>org.springframework.cloud</groupId>\n            <artifactId>spring-cloud-alibaba-dependencies</artifactId>\n            <version>0.9.0.RELEASE</version>\n            <type>pom</type>\n            <scope>import</scope>\n        </dependency>\n    </dependencies>\n</dependencyManagement>\n\n<dependencies>\n    <!-- Sample API -->\n    <dependency>\n        <groupId>org.springframework.cloud</groupId>\n        <artifactId>spring-cloud-dubbo-sample-api</artifactId>\n        <version>${project.version}</version>\n    </dependency>\n\n    <!-- Spring Boot dependencies -->\n    <dependency>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-web</artifactId>\n    </dependency>\n\n    <dependency>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-actuator</artifactId>\n    </dependency>\n\n    <!-- Dubbo Spring Cloud Starter -->\n    <dependency>\n        <groupId>org.springframework.cloud</groupId>\n        <artifactId>spring-cloud-starter-dubbo</artifactId>\n    </dependency>\n\n    <!-- Spring Cloud Nacos Service Discovery -->\n    <dependency>\n        <groupId>org.springframework.cloud</groupId>\n        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>\n    </dependency>\n</dependencies>\n```\n\n与应用 `spring-cloud-dubbo-server-sample` 不同的是，当前应用依赖 `spring-boot-starter-web`，表明它属于 Web Servlet 应用。\n\n> 以上完整的 Maven 依赖配置，请参考 `spring-cloud-dubbo-client-sample` [`pom.xml`](spring-cloud-dubbo-client-sample/pom.xml) 文件\n\n\n#### 配置 Dubbo 服务消费方\n\nDubbo 服务消费方配置与服务提供方类似，当前应用 `spring-cloud-dubbo-client-sample` 属于纯服务消费方，因此，所需的外部化配置更精简：\n\n```yaml\ndubbo:\n  registry:\n    # 挂载到 Spring Cloud 注册中心\n    address: spring-cloud://localhost\n  cloud:\n    subscribed-services: spring-cloud-alibaba-dubbo-server\n    \nspring:\n  application:\n    # Dubbo 应用名称\n    name: spring-cloud-alibaba-dubbo-client\n  main:\n    # Spring Boot 2.1 需要设定\n    allow-bean-definition-overriding: true\n  cloud:\n    nacos:\n      # Nacos 服务发现与注册配置\n      discovery:\n        server-addr: 127.0.0.1:8848\n```\n\n对比应用 `spring-cloud-dubbo-server-sample`，除应用名称 `spring.application.name` 存在差异外，`spring-cloud-dubbo-client-sample`\n新增了属性 `dubbo.cloud.subscribed-services` 的设置。并且该值为服务提供方应用 \"spring-cloud-dubbo-server-sample\"。\n\n- `dubbo.cloud.subscribed-services` : 用于服务消费方订阅服务提供方的应用名称的列表，若需订阅多应用，使用 \",\" 分割。\n不推荐使用默认值为 \"*\"，它将订阅所有应用。\n> 当应用使用属性 `dubbo.cloud.subscribed-services` 默认值时，日志中将会输出一行警告：\n> > Current application will subscribe all services(size:x) in registry, a lot of memory and CPU cycles may be used,\n> > thus it's strongly recommend you using the externalized property 'dubbo.cloud.subscribed-services' to specify the services\n\n由于当前应用属于 Web 应用，它会默认地使用 8080 作为 Web 服务端口，如果需要自定义，可通过属性 `server.port` 调整。\n\n> 以上完整的 YAML 配置文件，请参考 `spring-cloud-dubbo-client-sample` [`bootstrap.yaml`](spring-cloud-dubbo-client-sample/src/main/resources/bootstrap.yaml) 文件\n\n\n\n#### 引导 Dubbo Spring Cloud 服务消费方应用\n\n为了减少实现步骤，以下引导类将 Dubbo 服务消费以及引导功能合二为一：\n\n```java\n@EnableDiscoveryClient\n@EnableAutoConfiguration\n@RestController\npublic class DubboSpringCloudClientBootstrap {\n\n    @Reference\n    private EchoService echoService;\n\n    @GetMapping(\"/echo\")\n    public String echo(String message) {\n        return echoService.echo(message);\n    }\n\n    public static void main(String[] args) {\n        SpringApplication.run(DubboSpringCloudClientBootstrap.class);\n    }\n}\n```\n\n不仅如此，`DubboSpringCloudClientBootstrap` 也作为 REST Endpoint，通过暴露 `/echo` Web 服务，消费 Dubbo `EchoService` 服务。因此，\n可通过 `curl` 命令执行 HTTP GET 方法：\n\n```\n$ curl http://127.0.0.1:8080/echo?message=%E5%B0%8F%E9%A9%AC%E5%93%A5%EF%BC%88mercyblitz%EF%BC%89\n```\n\nHTTP 响应为：\n\n```\n[echo] Hello, 小马哥（mercyblitz）\n```\n\n以上结果说明应用 `spring-cloud-dubbo-client-sample` 通过消费 Dubbo 服务，返回服务提供方 `spring-cloud-dubbo-server-sample` 运算后的内容。\n\n\n\n## 高阶示例\n\n如果您需要进一步了解 Dubbo Spring Cloud 使用细节，可参考官方 Samples：https://github.com/spring-cloud-incubator/spring-cloud-alibaba/tree/master/spring-cloud-alibaba-examples/spring-cloud-alibaba-dubbo-examples\n\n其子模块说明如下：\n- spring-cloud-dubbo-sample-api：API 模块，存放 Dubbo 服务接口和模型定义\n- spring-cloud-dubbo-provider-web-sample：Dubbo Spring Cloud 服务提供方示例（Web 应用）\n- spring-cloud-dubbo-provider-sample：Dubbo Spring Cloud 服务提供方示例（非 Web 应用）\n- spring-cloud-dubbo-consumer-sample：Dubbo Spring Cloud 服务消费方示例\n- spring-cloud-dubbo-servlet-gateway-sample：Dubbo Spring Cloud Servlet 网关简易实现示例\n\n\n\n## 问题反馈\n如果您在使用 Dubbo Spring Cloud 的过程中遇到任何问题，请内容反馈至 https://github.com/spring-cloud-incubator/spring-cloud-alibaba/issues\n\n\n\n## 进阶阅读\n关于更多的 Dubbo Spring Cloud 特性以及设计细节，请关注 \n\n- Spring Cloud Alibaba wiki - https://github.com/spring-cloud-incubator/spring-cloud-alibaba/wiki\n\n- Dubbo 的博客：http://dubbo.apache.org/zh-cn/blog/index.html\n\n  \n\n## 下篇预告\n\n接下的文章将会介绍 Dubbo Spring Cloud 高阶示例的运用和实现，敬请关注小马哥微信公众号：`次灵均阁`\n\n[![img](https://camo.githubusercontent.com/36671f72e171f0c95f33f781f294b3e6bdceef07/68747470733a2f2f6d65726379626c69747a2e6769746875622e696f2f626f6f6b732f7468696e6b696e672d696e2d737072696e672d626f6f742f6173736574732f6d795f6d705f7172636f64652e6a7067)](https://camo.githubusercontent.com/36671f72e171f0c95f33f781f294b3e6bdceef07/68747470733a2f2f6d65726379626c69747a2e6769746875622e696f2f626f6f6b732f7468696e6b696e672d696e2d737072696e672d626f6f742f6173736574732f6d795f6d705f7172636f64652e6a7067)\n\n获得最新 Dubbo Spring Cloud 相关资讯。\n\n\n## [新书推荐](https://item.jd.com/12570242.html)\n\n\n本书全名为[《Spring Boot 编程思想》](https://mercyblitz.github.io/books/thinking-in-spring-boot/)，是以 Spring Boot 2.0 为讨论的主线，讨论的范围将涵盖 Spring Boot 1.x 的所有版本，以及所关联的 Spring Framework 版本，致力于：\n- 场景分析：掌握技术选型\n- 系统学习：拒绝浅尝辄止\n- 重视规范：了解发展趋势\n- 源码解读：理解设计思想\n- 实战演练：巩固学习成果\n\n![clipboard.png](/img/bVbqfh2)\n\n> 欢迎小伙伴在京东或当当订购\n京东存有少量现货（随机发送签名版），可先睹为快：http://t.cn/ExjBU2M\n当当价格优惠，需要五月初发货：http://t.cn/EX0QteF\n\n## 分享推荐\n\n### 免费分享\n\n- [「小马哥技术周报」](https://www.douyu.com/mercyblitz)\n\t- [斗鱼直播](https://www.douyu.com/mercyblitz)\n\t- [B 站录播](http://space.bilibili.com/327910845/channel/detail?cid=52311)\n- [「慕课网」](https://www.imooc.com/t/5387391)\n\t- [Spring Boot 2.0深度实践-初遇Spring Boot](https://www.imooc.com/learn/933) \n  - [Spring Boot 2.0深度实践之系列总览](https://www.imooc.com/learn/1058)\n- [「SegmentFault」](https://segmentfault.com/u/mercyblitz)\n\t- [「小马哥 2019 跨年直播」一入 Java 深似海，从此“劝退”成必然](https://mp.weixin.qq.com/s?__biz=MzIxNDU4NjE1OQ==&mid=2247484085&idx=1&sn=5905f53e69bae9d48b3783a83bde40b3)\t\n\n\n\n### 收费分享\n\n- [「小马哥 Java 知识星球」](http://t.cn/RnxUYzd)\n> 深入探讨 Java 相关技术，包括行业动态，架构设计，设计模式，框架使用，源码分析等。\n\n- SegmentFault 直播\n  - [《Java 微服务实践 - Spring Boot / Spring Cloud》](http://t.cn/RoC0nNi)\n  - [《一入 Java 深似海》](http://t.cn/E6bTa9O)\n\n- 慕课视频\n  - [《Spring Boot 2.0深度实践之核心技术篇》](http://t.cn/ReChCU9)\n\n- 慕课网\n    - [Spring Boot 2.0深度实践之核心技术篇](https://coding.imooc.com/class/252.html)\n    - Spring Boot 2.0深度实践之生态整合篇（即将上线...）\n\n\n## 关于作者\n\n小马哥，Java 劝退师，Apache 和 Spring Cloud 等知名开源架构成员，[点击查看详情](https://mercyblitz.github.io/about/)。\n\n---\n[^1]: Spring Cloud provides tools for developers to quickly build some of the common patterns in distributed systems (e.g. configuration management, service discovery, circuit breakers, intelligent routing, micro-proxy, control bus, one-time tokens, global locks, leadership election, distributed sessions, cluster state). - https://spring.io/projects/spring-cloud\n[^2]: Apache Dubbo (incubating) |ˈdʌbəʊ| is a high-performance, light weight, java based RPC framework. - https://dubbo.apache.org\n\n[^3]: Spring Cloud Alibaba provides a one-stop solution for distributed application development. - https://github.com/spring-cloud-incubator/spring-cloud-alibaba\n\n[^4]: Nacos is an easy-to-use dynamic service discovery, configuration and service management platform for building cloud native applications - https://nacos.io/\n\n[^5]: Eureka is a REST (Representational State Transfer) based service that is primarily used in the AWS cloud for locating services for the purpose of load balancing and failover of middle-tier servers. - https://github.com/Netflix/eureka\n\n[^6]: ZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services. - https://zookeeper.apache.org\n\n[^7]: Consul is a distributed service mesh to connect, secure, and configure services across any runtime platform and public or private cloud - https://www.consul.io/\n\n[^8]: Spring Cloud Open Feign - https://github.com/spring-cloud/spring-cloud-openfeign\n\n[^9]: 从 2.7.0 开始，Dubbo Spring Boot 与 Dubbo 在版本上保持一致\n\n[^10]: Preview releases of Spring Cloud Alibaba are available: 0.9.0, 0.2.2, and 0.1.2 - <https://spring.io/blog/2019/04/19/preview-releases-of-spring-cloud-alibaba-are-available-0-9-0-0-2-2-and-0-1-2>\n\n[^11]: 目前最新的 Spring Cloud “F” 版的版本为：`Finchley.SR2` - <https://cloud.spring.io/spring-cloud-static/Finchley.SR2/single/spring-cloud.html>\n\n[^12]: 当前Spring Cloud “G” 版为 `Greenwich.RELEASE`\n\n[^13]:  Spring Cloud 特性列表 - <https://cloud.spring.io/spring-cloud-static/Greenwich.RELEASE/single/spring-cloud.html#_features>\n\n[^14]:  Dubbo 2.7 开始支持配置中心，可自定义适配 - <http://dubbo.apache.org/zh-cn/docs/user/configuration/config-center.html>\n\n[^15]: Spring Cloud 原生注册中心，除 Eureka、Zookeeper、Consul 之外，还包括 Spring Cloud Alibaba 中的 Nacos\n\n[^16]: Dubbo 原生注册中心 - <http://dubbo.apache.org/zh-cn/docs/user/references/registry/introduction.html>\n\n[^17]: Alibaba Sentinel：Sentinel 以流量为切入点，从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性 - <https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D>，目前 Sentinel 已被 Spring Cloud 项目纳为 Circuit Breaker  的候选实现 - <https://spring.io/blog/2019/04/16/introducing-spring-cloud-circuit-breaker>\n\n[^18]: Spring Cloud Sleuth - <https://spring.io/projects/spring-cloud-sleuth>\n\n[^19]: Zipkin - <https://github.com/apache/incubator-zipkin>\n\n"
  },
  {
    "path": "_posts/2019-06-18-Service Mesh 时代，Dubbo 架构该怎么跟进？.md",
    "content": "> 原文链接：[Service Mesh 时代，Dubbo 架构该怎么跟进？](https://mp.weixin.qq.com/s/-p06WsPQK7EmFpmUABluRA)，来自于微信公众号：`次灵均阁`\n\n\n\n导读：6月21-23日，2019 GIAC全球互联网架构大会将于深圳举行。GIAC是面向架构师、技术负责人及高端技术从业人员的年度技术架构大会，是中国地区规模最大的技术会议之一。今年GIAC邀请到了众多布道师、明星讲师以及105位来自Google、微软、Oracle、eBay、百度、阿里、腾讯、商汤、图森、字节跳动、新浪、美团点评等公司专家出席。\n\n\n\n在大会前夕，高可用架构采访了本届 GIAC Java分论坛讲师[小马哥](https://mercyblitz.github.io/about/)，就目前大家广泛关注的Dubbo/微服务相关的问题进行了访谈。\n\n\n\n\n#### 作为 Duboo 核心开发者，请先简单介绍下自己\n\n答：大家好，我是小马哥（mercyblitz），一名学习当爸爸的父亲，[Java 劝退师](https://www.douyu.com/mercyblitz)，[Apache Dubbo](https://dubbo.apache.org/) PMC、[Spring Cloud Alibaba](https://github.com/spring-cloud-incubator/spring-cloud-alibaba)项目架构师，《Spring Boot 编程思想》的作者。目前主要负责集团中间件开源项目、微服务技术实施、架构衍进、基础设施构建等。\n\n\n\n#### Spring Cloud 和 Duboo 在微服务方面的优劣分别是什么？\n\n答：在 Java 生态中，Spring Cloud 和 Dubbo 都是微服务框架。前者被业界常作为 Java 微服务的首选框架，而后者有时被错误地解读为服务治理的 RPC 框架。实际上，两者在微服务架构中并没有本质的差异，均是分布式应用服务治理的框架。\n\n\n\n在开发体验方面，Spring Cloud 开箱即用的组件让人印象深刻。在 API 抽象和设计方面，流淌着 Spring 家族血液的 Spring Cloud 延续了父辈的荣耀。由此观之，Dubbo 与其存在差距。 \n\n\n\n然而随着实践的不断深入，Spring Cloud 功能的稳定性以及版本的兼容性等问题较为突出。当应用集群达到一定规模时，其分布式经验上的短板也随之暴露，尤其是 Spring Cloud Netflix 套件，比如 Eureka 与 Ribbon 之间的 90 秒延迟会影响服务调用的成功率，以及负载均衡算法缺少权重无法帮助 JVM 预热。简言之，在服务治理方面，Spring Cloud 相较于 Dubbo 而言，并不算太成熟。如果大家有兴趣了解更多的话，可参考[「小马哥技术周报」](https://github.com/mercyblitz/tech-weekly)。\n\n\n\n总之，Spring Cloud 和 Dubbo 各有特色，过度地关注彼此优劣并不可取。为此，Spring Cloud Alibaba 项目综合两家之长，提供了一套名为 Dubbo Spring Cloud 的整容实现，使得 Dubbo 与 Spring Cloud 不再是互斥性选项。\n\n\n\n#### 请介绍下 Duboo 的现状？\n\n答：2019年5月16日，Apache 软件基金会董事会决议通过了 Apache Dubbo 的毕业申请，这意味着 Apache Dubbo 正式成为 Apache 的顶级项目。Apache Dubbo 项目在 Github 上的 star 数已超过 2.7 万，contributors 人数达到 202，Commiters 人数也升至 32 人，借此机会感谢所有关系和参与 Apache Dubbo 建设的小伙伴。目前，项目主要包含三大核心的分支，均在并行开发。其中，2.6.x 处于维护状态；2.7.x 聚焦云原生微服务方向，3.0.x 则指定未来标准和技术走向。简言之，Dubbo 不再是纯粹的 Java 服务治理 RPC 框架，已经逐渐成为多语种 Cloud Native 基础设施的中坚力量。\n\n\n\n#### Duboo在成为Apache顶级项目的过程中，背后有哪些不为人知的故事？\n\n答：Dubbo 在 Apache 从孵化到毕业，期间的确有太多不为人知的故事，这里我简单地介绍一下其中孵化过程：\n\n- 筹备期（2017.12-2018.2）：最主要的工作是准备进入孵化器相关的材料，比如寻找合适的导师，编写加入孵化器的提案等。\n- 初始期（2018.2-2018.5）：主要完成的工作主要是完成知识产权的清理，邮件列表的创建，代码迁移等工作。\n- 首次 Release：Apache孵化项目第一个重要的里程碑，第一次Release非常关键，除了确保功能的稳定以外，最重要的就是需要确保引入的代码的许可证符合Apache的政策，Apache对于许可证有着明确的规定。\n- 社区发展（Community Building）：也是作为一个Apache项目非常看重的一环。最不愿意看到的就是一家公司独大，控制整个项目，对于Dubbo来说，经过这么多年的发展，在国内已经具备了一定的渗透率，有了不少用户，但是他们就像花粉一样散落在各个角落里面，需要做的事情就是把他们都聚集起来。\n\n\n\n#### 在未来一年，Duboo的新特性路线图可以简单介绍下吗？\n\n答：由于 Dubbo 2.6.x 处于维护状态，不会新增明显的功能特性。\n\n\n\n本年度主要的发力点在 Dubbo 2.7 这个版本上，该版本致力于 Cloud Native 以及微服务领域，大致的路线计划为\n\n- 2.7.2 - Metrics、etcd 元数据、nacos 配置与元数据以及 2.6 兼容\n- 2.7.3 - Cloud Native 注册机制、服务自省以及 Dubbo Proxy\n- 2.7.4 - K8s 原生支持（服务发现、元数据存储和配置推送）、Dubbo GO 以及 gRPC 集成\n- 2.7.5 - 服务治理规则支持 Pilot CRD \n- 2.7.6 - 控制面 xDS API 对接\n\n\n\nDubbo 3.0.0 M1 版本的核心特性围绕在 RSocket、Reactive 以及异步化上的支持。 M2 版本计划将在 8 月发布，主要提供对 HTTP/2 的支持和性能调优。接下来的 M3 版本将通过 HTTP/2 支持 gPRC 以及 Rocket 通讯协议。3.0.0 正式的发布将安排在 2020 年的 2 月。\n\n\n\n\n\n#### Duboo 开源以来，代码贡献者中阿里本身的开发者占绝大多数，这是否意味着来自阿里的需求会起主导作用？在后续的发展过程中，计划如何引入阿里之外的开发者？\n\n答：尽管目前 Apache Duboo 代码贡献者主要来自于阿里的开发人员，不过这个比重正在迅速地变小，一方面说明  Dubbo 用户人数在逐渐变大，专业程度在不断地变深，同时，也说明有意主导并且贡献的小伙伴越来越活跃。这无论对社区的发展，还是从业人员的职业技能均有裨益。因此，需求的来源不再已阿里为绝对主导，社区共建和共制的发展模式已成事实。\n\n\n\n\n\n#### Service Mesh 时代，需要什么样的微服务框架？\n\n答：哈哈，需要 Dubbo 这样的框架（玩笑）。首先，个人并不是 Service Mesh 方面的专家，就目前所得到掌握的信息，Service Mesh 并不算成熟的技术，换言之，目前还不是 Service Mesh 的时代，甚至我听到不少的朋友由于性能和稳定性方面的原因，从该架构中退化，这也是 Dubbo 在 Service Mesh 方面的衍进相对缓慢的原因之一。当然，技术的发展总会是在掌声伴随着嘘声中前进，因此，个人对于 Service Mesh 的看法是谨慎乐观的。\n\n\n\n\n\n#### 对于公司内部定制Dubbo而言，你有什么建议吗？\n\n答：流水不腐户枢不蠹，我希望这些公司能够积极参与 Dubbo 社区的共建，或许这些定制化的场景也可以服务其他场景。大家互通有无，实现共同进步。\n\n\n\n\n\n#### 对于初级开发者而言，学习Duboo应该如何入手？对于资深开发者而言，怎样研究Dubbo的源代码更加高效？\n\n答：对于初级开发者，我的建议是首先从 Apache Dubbo 官网（[https://dubbo.apache.org](http://dubbo.apache.org/)）学习《用户文档》，初步了解了 Dubbo 架构和特性后，再结合 Dubbo 官方样例（https://github.com/apache/dubbo-samples）全面掌握 Dubbo 功能和最佳实践。最后，参考官方博客（[http://dubbo.apache.org/zh-cn/blog/index.html](http://dubbo.apache.org/zh-cn/blog/index.html)），深度理解 Dubbo。\n\n对于资深开发者，尤其是那些致力于贡献的小伙伴，我建议参考《开发者文档》，掌握 Dubbo 设计和实现，并且结合 Dubbo 的源码巩固学习，最好直接贡献代码（在 GitHub Pull Request），战胜心中一切的畏惧。如果仍不满足于此，强烈推荐参考 Apache Dubbo PMC 商宗海（花名：诣极）编写并即将出版的书籍 - 《深入理解 Apache Dubbo 与实战》，从中本人也受益匪浅，建议小伙们入手。\n\n\n\n\n\n#### 你作为讲师参加GIAC，对本次GIAC大会有何寄语？\n\n答：非常感谢 GIAC 的主办方给本人这次机会分享 Dubbo 相关的的议程，这也是我本人第二次在 GIAC 分享该主题了。我衷心地祝福 GIAC 影响力越做越大，希望能够走出国门，成为具有国际化影响力的技术组织，向世界传播技术和力量。\n\n\n\n\n\n#### 作为Duboo的开发者，你最喜欢的Java（Java8以后）特性是什么？你最希望加入的Java特性是什么？\n\n答：Java 8 是 Dubbo 2.7 默认的语言级别，其中 Lambda 表达式以及 Stream API 被广泛地使用。除此之外，本人同样偏好使用 CompletableFuture 作为并行编程的 API。我最希望 Java 增加 JVM 级别的协程支持。\n\n\n\n\n\n#### 简单介绍下你自己的从业经历？\n\n答：今年是我从业的第十二个年头，这些年一直在从事 Java 研发。首个雇主是一家外企公司，为其服务了三年。外企的工作相对轻松，拥有充分的自主时间提升技能，同时也有机会提升英语水平。期间通过了 SUN Java（SCJP、SCWCD、SCBCD）以及 Oracle OCA 等的认证，尽管这些证书并没有受到国内雇主的重视，不过对我后续的职业产生了深远的影响。当然，事情并不是总是积极正面，东西方文化差异，以及部分外籍同事的傲慢与偏见着实让本人对西方的技术和文化重新开始审视。既然无法改变，那么离开并继续深造或许是必然的选择，希望有一天能够通过共同的努力，让世界看到中华的进步。于是，我的第一份工作就在 2010 年 10 月 1 号画上了句点。迎接我的是第二份工作，至今也快九个年头。这几年，我经历了很多、学到了很多，也成长了很多，岂能尽如人意，但求无愧我心。不可否认的是，儒家思想对我的影响最为深刻，它让我学会独立、理性以及辩证的思考，培养我处变不惊的人生态度，直接或间接地提升了专业素质。经过数年的沉寂，我也明确了自己的方向，辗转投入开源社区的建设。不过，纵使浑身是铁能打几根钉，开源社区的发展需要更多的能人参与，知之者不如好之者，好之者不如乐之者。然而现实的情况又有些残酷，不少的年轻人在经济的压力下，逐渐失去对技术的追求。于是从 2016 年开始，我便尝试做一些技术分享，希望能够帮助到部分年轻从业人员，使他们对技术产生兴趣。随后，我又着手编写《Spring Boot 编程思想》，希望读者能够理解规范和基础的重要性，如果读者从中能够培养自己系统化的知识体系或者思维方式，那就善莫大焉了。我也时常鼓励更多的小伙伴多多分享，无论是免费，还是收费。同时，注重知识产权的保护，树立良好的生态环境。当然，我的第二份职业尚未告一段落，或许等它结束之际，方可“盖棺定论“。总之，但行好事，莫问前程。\n\n\n\n\n#### [书籍推荐](https://item.jd.com/12570242.html)\n\n\n-《Spring Boot 编程思想（核⼼心篇）》 https://item.jd.com/12570242.html\n\n> 本书全名为[《Spring Boot 编程思想》](https://mercyblitz.github.io/books/thinking-in-spring-boot/)，是以 Spring Boot 2.0 为讨论的主线，讨论的范围将涵盖 Spring Boot 1.x 的所有版本，以及所关联的 Spring Framework 版本，致力于：\n> - 场景分析：掌握技术选型\n> - 系统学习：拒绝浅尝辄止\n> - 重视规范：了解发展趋势\n> - 源码解读：理解设计思想\n> - 实战演练：巩固学习成果\n\n- [《Spring Cloud 微服务实战》](https://item.jd.com/12172344.html)\n- [《深⼊入理理解Kafka：核⼼心设计与实践原理理》](https://item.jd.com/12489649.html)\n- [《未来架构 从服务化到云原⽣生》](https://item.jd.com/12498217.html)\n- [《⾼高可⽤用可伸缩微服务架构：基于Dubbo、Spring Cloud和Service Mesh》](https://item.jd.com/12585284.html) \n- [《Kubernetes权威指南：从Docker到Kubernetes实践全接触》](https://item.jd.com/12601558.html)\n- [《Java编程⽅方法论：响应式RxJava与代码设计实战》](https://item.jd.com/12615848.html)\n\n"
  },
  {
    "path": "_posts/2019-09-05-2019 Java 趋势报告 InfoQ 采访稿（小马哥部分）.md",
    "content": "### 趋势报告框架\n\n#### 第一部分：Java的技术采用生命周期\n\n这部分采用与英文站同样的标准划分：\n\n- 创新者\n\n- 早期采用者\n\n- 早期大众\n\n- 晚期大众\n\n技术采用生命周期是美国高科技营销大师杰弗里·摩尔在自己的书《跨越鸿沟》里提出的概念。技术采用生命周期是一个用来衡量用户对某项新技术接受程度的模型，它认为一个新的技术，从一开始出现到最后走向成熟，必然会经历创新者、早期采用者、早期大众、晚期大众的阶段。\n\n\n\n虽然每个人群间都会有裂缝，但是早期采用者和早期大众之间的那条裂缝最大，这条裂缝就是传说中的“鸿沟”，只有跨越过这条鸿沟，渗透到早期大众这个人群，产品才等于是进入了主流市场。\n\n\n\n希望您结合国内使用和发展情况，把以下技术对应到技术采用生命周期相应的不同阶段中：\n\n- Java/JVM\n  - Java版本（8～13）；\n  - OpenJDK定制版或者公开发行版，Oracle JDK, OpenJDK by Oracle/Redhat/Azul/Alibaba/Amazon, 或者其他；\n  - 非Hotspot JDK生产实践，如GraalVM、IBM OpenJ9；\n  - 语法与特性，例如：Lambda /Stream、Vector API等（可以从是否有哪些特性带来了突出甚至不可替代的生产价值的角度，评判他们在技术采用生命周期中的位置）；\n  - JVM语言，Kotlin、Scala、Groovy、其他；\n\n- 不同层次的主流框架：Java EE（Jakarta EE）、Spring Framework、RxJava 、Vert.x、Netty；\n\n- 微服务领域：Spring Boot/Cloud、Dubbo、TarsJava、ServiceComb、其他\n\n\n\n> [小马哥（@mercyblitz）](https://mercyblitz.github.io/about/)：\n>\n> | 创新者                    | 早期采用者  | 早期大众         | 晚期大众                     |\n> | ------------------------- | ----------- | ---------------- | ---------------------------- |\n> | Java 13                   | Java 11     | OpenJDK          | Java 8                       |\n> | Jakarta EE                | GraalVM     | Reactive Streams | Lambda/Stream                |\n> | Apache Dubbo (ECO System) | Vert.x      | Kotlin           | Scala、Groovy                |\n> |                           | TarsJava    | RxJava/Reactor   | Java EE（Jakarta EE）、Netty |\n> |                           | ServiceComb |                  | Spring Framework             |\n> |                           |             |                  | Spring Boot/Cloud            |\n> |                           |             |                  | Apache Dubbo                 |\n\n![img](https://uploader.shimo.im/f/PlaoGDZsEGoeqwtL.png!thumbnail)\n\nInfoQ英文站结果供参考\n\n\n\n#### 第二部分：Java趋势点评\n\n对第一部分中您所归类的处于不同阶段的技术，请您逐一做出如下点评问题包括：\n\n- 某个技术为什么要被划在这个技术采用生命周期内？这个技术在国内的发展情况以及机遇和挑战是什么？\n\n> [小马哥（@mercyblitz）](https://mercyblitz.github.io/about/)：\n>\n> - Java / JVM 语言 - Java 8 已被业界普遍接受，无论像 Spring Framework、Spring Boot，以及 Spring Cloud 这样的现代 Java 框架，还是类似于 Vert.x、RxJava 或 Reactor 这类小众框架，均已构建在 Java 8 以及更高的版本之上。同时，Lambda 语法以及 Stream API 也在开发人员的日常工作中广泛地运用，并且没有看到语法回退的趋势。因此，Java 8 和 Lambda/Stream 可归类为“**晚期大众**”。 JVM 语言 Scala 和 Groovy 已快成为明日黄花，往昔的光芒逐渐地被后期之秀 Kotlin 替代，故 Scala  和 Groovy  属于“**晚期大众**”，而 Kotlin 则纳入”**早期大众**“ 之流。然而 Java 9 的被接受程度则没有那么幸运，尽管我们等待它的到来已有数年。 Java 模块化作为 Java 9 核心的特性，就我个人而言，完全能够理解和接受它的设计。虽然模块化增强了模块的隔离性，减少了内存的 Footprint，然而，它更强的封装性无形之中增加了管理依赖的成本。所谓曲高和寡，夸张地说，模块化形同虚设。因此，应用升级 Java 9 的效果相当于 Java API 以及 JVM 的更新。同时，Oracle 宣布从 Java 9 开始，每半年将更新一个 Java 大版本。那么，更多的人会选择 Java 11 这样的长期支持（Long-Term -Support, LTS）版本，换言之，Java 9/10 则成了过渡版本（non‑LTS）[^1]。因此，Java 11 将是未来 Java 用户的最可能选项，将其列为“早期采用者”。至于 Java 13，最近有注意到该本版在新 GC 算法的提升以及 Socket 实现上的变化[^2]，还是非常令人期待的，故排在“**创新者**” 之列。除了模块化之外，Java 9 还有大量的 API 更新，在偏开发侧的部分，Flow API 可能是最有吸引力的，提供了 Reactive Streams[^3] 标准接口以及实现，并且内建了 HTTP Client Reactive 实现。尽管 Spring 和 Eclipse 社区大力的推广，并且 Java Lambda 以及 Stream API 也流行开来，不过对开发者而言，Reactive Streams 技术还是相对陌生，将其放在 “**早期大众**“。同理，Spring 引入的 Reactor 框架也属于“**早期大众**” 阶段。虽然 RxJava 并非 Reactive Streams 的实现，但是相较于前两者而言，它在 Reactive 编程中的地位必然是有过之而无不及的，故也在 “**早期大众**“ 之中。\n>\n> - OpenJDK - 由于 Oracle 宣布 2019 年伊始，Oracle  JDK 8 以及更高版本在服务器端部署不再免费，OpenJDK 则成为大多数 Java 用户的选项。尽管 Oracle JDK 与 OpenJDK 几乎出至于同一家之手，不过OpenJDK 很可能被认为是一种退而求其次的选择。对于具备自主研发的企业，它们可能选择在 OpenJDK 的基础上，自定义分支继续开发。在一定程度上，Java 的发展方向出现了裂痕，所以未来仍存在着不确定性，故将其放置于 “**早期大众**“\n> - 非Hotspot JDK生产实践 - 按照 GraalVM 官方的描述，GraalVM 将会是下一代的 JVM 基础设施，也是 Oracle 的重点项目，能够将传统的 JVM 进程 native 化，那么未来 Java 的性能提升以及快速启停则不再遥远，不够目前还尚不可知其兼容性情况以及明确的商业化条款，列为“**早期采用者** 应该是合理的\n>\n> - 不同层次的主流框架\n>   - Java EE  -  在 Java 生态中，绝大多数应用直接或间接地使用了 Spring Framework，这个曾经以轻量级著称的框架，目前显然“名不副实”，不过巨大的用户基础，早已进入“**晚期大众**”的行列。相反，Java EE 规范或者重组后的 Jakarta EE[^4] 则寂寥许多。实际上，作为 J2EE 或 Java EE 的模仿者，尽管 Spring Framework 多半的特性和实现向 JSR[^5] 参考。一定程度上，Spring Framework 的流行“压缩”了 Java EE 以及 JSR 的认知空间，因此不少的开发人员不知道 Java EE API 或者 JSR 的存在，尤其是年轻的国内从业人员。当然，Spring 也反哺了少量标准给 Java EE，比如 JSR 330[^6]，因此，将 Java EE 列为 **“晚期大众**” 是毋容置疑的。值得一提的是，Jakarta EE 在 Eclipse 基金会[^7]的带领下能否再次“伟大” 值得期待，后续未来 Jakarta EE 将是“**创新者**”成员\n>   - 网络框架 - Java 的网络框架只有两种，其一是 Netty，其二则是其他。如此描述也丝毫不夸张，毕竟大多数与网络相关的框架或中间件都和 Netty 多少存在关联，如 Apache Dubbo、Spring 5 Web Server、Jersey 等，所以**“晚期大众**”的排行众望所归。\n>   - 微服务框架 - Java 微服务框架的王者非 Spring Boot 和 Spring Cloud 莫属，进过数年的生产使用，两者早已属于“**晚期大众**” 的技术栈。相对应地，Apache Dubbo 出现和开源的时间比 Spring Cloud 早不少，无论是性能还是稳定性，相对于后者要优秀不少，因此，Apache Dubbo 也属于 “**晚期大众**” 框架。不过，最新的 Apache Dubbo ECO System（生态系统）则基于 Apache Dubbo 衍进的 Cloud Native 解决方案，目前尚未枝叶茂盛，处于 “**创新者**” 阵营。而小众 的 Vert.x 则由于编程模型以及 API 熟悉度等客观条件所限，它不得不仍处于 ”**早期采用者**”。类似，TarsJava 以及 ServiceComb 最近才出现，用户的认可度和稳定性稍微成熟，同样处于 ”**早期采用者**”。\n>\n> [^1]: Oracle Java SE Support Roadmap - https://www.oracle.com/technetwork/java/java-se-support-roadmap.html\n> [^2]: Java Performance Tuning News August 2019 - http://www.javaperformancetuning.com/news/news225.shtml\n> [^3]: Reactive Streams JVM - https://github.com/reactive-streams/reactive-streams-jvm\n> [^4]: Jakarta EE - https://jakarta.ee/\n> [^5]:  Java Community Process - https://jcp.org/en/home/index\n> [^6]: JSR 330: Dependency Injection for Java  - https://jcp.org/en/jsr/detail?id=330\n> [^7]: Eclipse Foundation -  https://www.eclipse.org/org/foundation/\n\n\n\n- 国内是否在某个相对完整的领域，形成甚至开始引领技术趋势？\n\n> [小马哥（@mercyblitz）](https://mercyblitz.github.io/about/)：\n>\n> 在国内的开源软件中，Apache Dubbo（后文简称 Dubbo）常年受到业界的青睐，并荣获多次殊荣。个人认为 Dubbo 有机会引领技术趋势。Dubbo 从过去的高性能 RPC 框架，正在走向 Cloud Native 的生态系统（后文简称 Dubbo ECO System）。具体执行的步骤也是 Dubbo 社区对 Cloud Native 的发展趋势研判，首要场景是 Dubbo 在 Spring Cloud 之间的整合。这部分工作已在 Spring Cloud Alibaba 项目中完成，作为项目中的核心成员，Dubbo Spring Cloud 可无缝地替换过传统 Spring Cloud OpenFeign，不仅提供更好的性能，并且具备更多的负载均衡策略和熔断等特性。同时，灵活的扩展点和内建实现帮助 Dubbo 适应不同语言、环境和基础设施。随着 Dubbo 内核即将发布 Cloud Native（即将发布）特性 ，能够将 Dubbo 帮助独立于任意的基础设施，实现 Dubbo（原生）与 Spring Cloud（原生）调用互通，甚至在 K8S 场景下。无论 Dubbo 身处何处，统一的编程模型帮助开发人员快速和高效地实现业务逻辑，达成 “**Write Once, Run Anywhere**” 的目的。后续，Dubbo 在多语言、Mesh 化 以及 Istio 的建树将逐一呈现。\n\n\n\n#### 第三部分：应用实践\n\n请您尽可能详细地回答以下问题，这些问题的回答除了作为趋势报告的一部分之外，还可以作为您所在企业的技术实践，单独成文（InfoQ记者将针对每位嘉宾所在企业和技术实践，补充个性化问题）：\n\n\n\n**Java相关：**\n\n- 您的企业使用的JDK版本情况，是否采用了某个OpenJDK 发行版？您如何看待OpenJDK 在国内的发展？（如果没有采用，原因以及后续计划？）\n\n  > [小马哥（@mercyblitz）](https://mercyblitz.github.io/about/)：\n  >\n  > 在开源方面， OpenJDK 的选择仅为 Oracle 官方提供。如果是公司内部的话，则是  OpenJDK Alibaba 的分支。OpenJDK 在国内直接拿去使用的多，有能力围之扩展的公司凤毛菱角。可能随着 OpenJDK 不同分支的成熟，未来国内公司选择面将会更广\n\n- 您的企业目前在支持Java技术栈方面的策略是什么？计划和目标是什么？相关的核心痛点或者业务需求是什么？\n\n  > [小马哥（@mercyblitz）](https://mercyblitz.github.io/about/)：\n  >\n  > 关于公司在战略层面的设定，个人是不是非常清楚的。不过，作为一名基础设施的研发人员，自身遇到和周遭听到同事抱怨的痛点还是存在的。比如，Java 9 之后的版本更新问题。 Reactive Streams 推广和落地时的阻碍，毕竟大多数开发人员从事业务开发，业务系统的稳定性是他们核心 KPI 之一，他们缺少足够的勇气和动力蒸腾新技术的引进、也没有时间和精力关注其中细节，尤其当没有明显特性变化的基础设施 和 API 升级时。对于本人而言，我比较关心 JVM 中的变化，包括 GC 算法和性能提升，比如 C1 和 C2 编译所导致的延迟和资源开销，如何帮助 JVM 快速启停，因为这些会限制 Java 作为编程语言在云原生时代的想象空间\n\n- 对于当前Java的整体发展情况，您有什么感想？\n\n  > [小马哥（@mercyblitz）](https://mercyblitz.github.io/about/)：\n  >\n  > Java 目前仍具在编程语言排行榜上夺魁，不过在整体比重上微幅下滑。个人看来，未来这个趋势还将持续。究其原因，一方面是由于新语种出现的中短期效应，一方面是 Java 的编程复杂度并没有明显的降低，比如 I/O 处理、并发/并行计算，以及类加载等等。再者是 Java 与操作系统之间的交互仍不够充分，尽管 Java 9 开始提供了不少的 API，然而了解和使用的群体不足。Java 在这方面明显不及 GO 语言。\n  >\n  > 从语言层面来看，Java 正在向主流非 Java 语言融合，解决其中鸿沟的起手式就是语法的变化，比如 Java 8 的 Lambda 表达式 和 Java 10 的局部变量类型（`var` ）等。个人认为这是一件好事，未来前后端不分家，相互渗透，对于彼此语言都是良性发展。\n  >\n  > 除此之外，个人比较期待的是 GraalVM 对 Java 的改变，传统 Java 应用必须依赖 JVM 进程加载字节码后解释执行，无法保证所有的代码能够在运行期编程完成，不免有运行时编译所带来的性能开销，从而影响 JVM 的启停时间，简单地说，这种方式不够 Native，对于云原生或许不够友好。如果未来 GraalVM 的社区版也能够像 OpenJDK 那般“亲民”，那么，Java 的变化将是颠覆性的\n\n\n\n**微服务相关：**\n\n- 请介绍您的企业是否进行了微服务实践？如果是，在整体系统架构中的比例是多少？如果不是，是否有相关计划？\n\n  > [小马哥（@mercyblitz）](https://mercyblitz.github.io/about/)：\n  >\n  > 大多数应用已实施微服务架构，微服务应用的比重高达8成以上\n\n- 您所采用的主要微服务框架是什么？如何判断国内该领域的技术发展情况？您认为微服务主流框架的争夺是否尘埃落定？\n\n  > [小马哥（@mercyblitz）](https://mercyblitz.github.io/about/)：\n  >\n  > 采用的微服务架构主要以 Spring Boot/Cloud 以及 Apache Dubbo 为主，同时这也是国内大多数公司作为微服务框架的选择。不过，在国内，一直都有 Spring Cloud 与 Apache Dubbo 的优劣之争，然而这个争端似乎没有持续太长的时间，因为随着 Dubbo Spring Cloud（作为 Spring Cloud Alibaba 的一部分）发布之后，强强联合，互补互助\n\n- 您如何看待Service Mesh在国内的发展现状和发展前景？\n\n  > [小马哥（@mercyblitz）](https://mercyblitz.github.io/about/)：\n  >\n  > 个人对 Service Mesh 的看法是乐观偏谨慎的，一方面，作为从业人员，对于技术总有猎奇的心态。另外一方面，这个技术在设计上存在一些理想主义，比如，性能损耗以及稳定性，并且分布式场景中的典型问题并没有得到解决和改善，比如数据一致性、分布式事务等。据我所知，国内不少的互联网公司，如阿里巴巴、蚂蚁金服以及美团等已经开始在生产环境试点 Service Mesh，听起来这是一件好事。前沿的技术总有有人去探险。如果成功，前人栽树，后人乘凉。至于它能否成功，主要看市场是否愿意买单\n\n\n\n## 参与专家：\n\n[小马哥（@mercyblitz）](https://mercyblitz.github.io/about/)，《Spring Boot 编程思想》作者、Apache Dubbo PMC 和 Spring-Cloud-Alibaba Architect"
  },
  {
    "path": "_posts/2020-05-11-Apache Dubbo 服务自省架构设计.md",
    "content": "# 背景\n\n随着微服务架构的推广和普及，服务之间的耦合度在逐步降低。在演化的过程中，伴随着应用组织架构的变化以及基础设施的衍进，服务和应用之间的边界变得更为模糊。Java 作为一门面向对象的编程语言，Java 接口（interface）作为服务之间通讯的一等公民，配合文档（JavaDoc）便于开发人员理解和维护。基于相同的编程哲学，Apache Dubbo 作为传统的 RPC 服务治理框架，通过接口实现分布式服务。然而对于微服务治理而言，应用（或“服务”）才是基础设施的核心要素。面对云原生（Cloud Native）技术的兴起，传统的 Dubbo 架构不断地面临着新的的挑战。下面内容将以 Apache Dubbo 2.7.5 为基础，介绍全新架构 - Apache Dubbo 服务自省（后文简称“服务自省”），了解 Dubbo 传统架构所面临的现实挑战，以及服务自省架构的设计和解决之道。\n\n\n\n\n\n# 术语约定\n\n- Service：SOA 或微服务中的“服务”，或称之为“应用”，具有全局唯一的名称\n- Service Name: 服务名称，或应用名称\n- Servce Instance：服务实例，或称为应用实例（Application Instance），表示单个 Dubbo 应用进程\n- Registry：注册中心\n- Dubbo 服务：又称之为“Dubbo 业务服务”，包含 Java 接口、通讯协议，版本（version）和分组（group）等元信息\n- Dubbo 服务 ID：唯一鉴定 Dubbo 服务的元数据，用于 Dubbo 服务暴露（发布）和订阅\n- Provider：Dubbo 服务提供方\n- Consumer：Dubbo 服务消费方\n- Dubbo 服务暴露：也称之为 Dubbo 服务发布，或英文中的“export”、\"exported\"\n- Dubbo 应用服务：也称之为 Dubbo 业务服务，或业务 Dubbo 服务\n\n\n\n\n\n# 使用场景\n\n服务自省是 Dubbo 应用在运行时处理和分析 Dubbo 服务元信息（Metadata）的过程，如当前应用暴露 的Dubbo 服务以及各自的通讯协议等。期间会伴随着事件的广播和处理，如服务暴露事件。Dubbo 服务自省架构是其传统架的一种补充，更是未来 Dubbo 架构，它更适合以下使用场景：\n\n- 超大规模 Dubbo 服务治理场景\n- 微服务架构和元原生应用\n- Dubbo 元数据架构的基石\n\n\n\n## 超大规模 Dubbo 服务治理场景\n\n如果 Dubbo 集群规模超过一千以上，或者集群扩缩容已无法自如地执行，如 Zookeeper 管理数万 Dubbo 服务，服务自省可极大化减轻注册中心的压力，尤其在内存足迹、网络传输以及变更通知上体现。\n\n\n\n## 微服务架构和元原生应用\n\n如果想要 Dubbo 应用更好地微服务化，或者更接近于云原生应用，那么服务自省是一种不错的选择，它能够提供已应用为粒度的服务注册与发现模型，全面地支持最流行的 Spring Cloud 和 Kubernetes 注册中心，并且能与 Spring Cloud 或 Spring Boot 应用交互。 \n\n\n\n## Dubbo 元数据架构的基石\n\nDubbo 元数据架构是围绕 Dubbo DevOps 而引入，包括 Dubbo 配置元数据（如：属性配置、路由规则等）和结构元数据（如：Java 注解、接口和文档等）。服务自省作为 Dubbo 元数据的基础设施，不仅支持所有元数据的存储和平滑升级，而且不会对注册中心、配置中心和元数据中心产生额外的负担。\n\n\n\n\n\n# 传统架构\n\nApache Dubbo 是一款面向接口代理的高性能 RPC 框架，提供服务注册与发现的特性，其基础架构如下图所示：\n\n![image.png](/img/assets/dubbo/2020-05-11/1.png)\n\n**(图 1）**\n\n\n\n- **Provider** 为服务提供方，提供 Java 服务接口的实现，并将其元信息注册到 Dubbo 注册中心（过程 1.register 所示）\n\n\n\n- **Consumer** 为服务消费端，从 Dubbo 注册中心检索订阅的 Java 服务接口的元信息（过程 2.subscribe 所示），通过框架处理后，生成代理程序执行远程方法调用（过程 4.invoke 所示）\n\n\n\n- **Registry** 为注册中心，属于注册元信息中心化基础设施（如 Apache Zookeeper 或 Alibaba Nacos），为 Provider 提供注册通道，为 Cosumer 提供订阅渠道。同时，注册中心支持注册元信息变更通知，通知 Consumer 上游 Provider 节点的变化（如扩容或缩容）。而注册元信息均以 Dubbo URL 的形式存储\n\n\n\n- **Monitor** 为服务治理平台，提供开发和运维人员服务查询、路由规则、服务 Mock 和测试等治理能力\n\n\n\n综上所述，**Dubbo 注册与发现的对象是 Dubbo 服务（Java 接口****），而其载体为****注册元信息，即** **Dubbo URL**，如：`dubbo://192.168.1.2:20880/com.foo.BarService?version=1.0.0&group=default`，通常包含必须信息，如服务提供方 IP 和端口、 Java 接口，可选包含版本（version）和分组（group）等。服务 URL 所包含的信息能够唯一界别服务提供方的进程。\n\n\n\n\n\n# 现实挑战\n\n为了更好地符合 Java 开发人员的编程习惯，Dubbo 以 Java 服务接口作为注册对象，所面临的现实挑战主要有：\n\n- 如何解决或缓解注册中心压力过载\n- 如何支持以应用为粒度的服务注册与发现\n- 如何精简 Dubbo URL 元数据\n\n\n\n## 如何解决或缓解注册中心压力过载\n\n\n\n### 注册中心内存压力\n\nDubbo 注册中心是中心化的基础设施，大多数注册中心的实现为内存型存储，比如 Zookeeper、Nacos 或 Consul、Eureka。注册中心的内存消耗与 Dubbo 服务注册的数量成正比，任一 Dubbo Provider 允许注册 N 个 Dubbo 服务接口，当 N 越大，注册中心的负载越重。根据不完全统计，Dubbo 核心 Provider 用通常会暴露 20 ~ 50 个服务接口。注册中心是中心化的基础设施，其稳定性面临严峻考验。尽管微服务架构不断地深化，然而现实情况是，更多开发者仍旧愿意在单一 Provider 上不断地增加 Dubbo 服务接口，而非更细粒度的 Dubbo Provider 组织。\n\n\n\n### 注册中心网络压力\n\n为了避免单点故障，主流的注册中心均提供高可用方案。为解决集群环境数据同步的难题，内建一致性协议，如 Zookeeper 使用的 Zab 协议，Consul 采用的 Raft 协议。无论哪种方式，当 Dubbo URL 数量变化频繁时，网络和 CPU 压力也会面临考验。如果注册中心与客户端之间维持长连接状态的话，如 Zookeeper，注册中心的网络负担会更大。\n\n\n\n### 注册中心通知压力\n\n假设某个 Dubbo Provider 注册了 N 个 Dubbo 服务接口，当它扩容或缩容 M 个实例（节点）时，N 数量越大，注册中心至少有 M * N 个 Dubbo URL 注册或移除。同时，大多数注册中心实现支持注册变化通知，如 Zookeeper 节点变化通知。当 Dubbo Consumer 订阅该 Provider 的 Dubbo 服务接口数为 X 时，X 数值越大，通知的次数也就越多。实际上，对于来自同一 Provider 的服务接口集合而言，X-1 次通知是重复和无价值的。\n\n\n\n如果 Dubbo 注册实体不再是服务 URL，而是 Dubbo Provider 节点的话，那么上述情况所描述的注册中心压力将得到很大程度的缓解。（负载只有过去的 1/N 甚至更少），然而 Dubbo 如何以应用为粒度来注册又是一个新的挑战。\n\n\n\n## 如何支持以应用为粒度的服务注册与发现\n\n尽管 Dubbo 也存在应用（Application）的概念，不过传统的使用场景并非核心要素，仅在 Dubbo Monitor 或 Dubbo Admin 场景下做辨识之用。随着微服务架构和云原生技术的兴起，以应用为粒度的注册模型已是大势所趋，如 Spring Cloud 和 Kubernetes 服务注册与发现模型。注册中心所管理的对象通常与业务无关，甚至不具备 RPC 的语义。在术语上，微服务架构中的“服务”（Services）与云原生中“应用”（Applications）是相同的概念，属于逻辑名称，而它们的成员则以服务实例（Service Instances）体现，服务和服务实例的数量关系为 1:N。\n\n\n\n单个服务实例代表一个服务进程，而多个 Dubbo 服务 URL 可隶属一个 Dubbo Provider 进程，因此，Dubbo URL 与服务实例的数量关系是 N : 1。假设一个 Dubbo Provider 进程仅提供一个 Dubbo 服务（接口）的话，即 N = 1 的情况，虽然以应用为粒度的服务注册与发现能够基于 Dubbo 传统的 Registry SPI 实现，不过对于现有 Dubbo 应用而言，将存在巨大的应用微服务化工作。\n\n\n\n### 支持 Spring Cloud 服务注册与发现模型\n\nSpring Cloud 是 VMware 公司（前为 Pivotal）推出的，一套以 Spring 为技术栈的云原生（Cloud-Native）解决方案，在 Java 微服务领域具备得天独厚的优势，拥有超大规模的全球用户。Spring Cloud 官方支持三种注册中心实现，包括：Eureka、Zookeeper 和 Consul，Spring Cloud Alibaba 扩展了 Nacos 注册中心实现。 尽管 Zookeeper、Consul 和 Nacos 也被 Apache Dubbo 官方支持，然而两者的服务注册与发现的机制不尽相同。\n\n若要 Dubbo 支持 Spring Cloud 服务注册与发现模型，Dubbo 则需基于 Dubbo Registry SPI 实现，否则底层的变化和兼容性存在风险。\n\n\n\n### 支持 Kubernetes 服务注册与发现模型\n\nKubernetes 源自 Google 15 年生产环境的运维经验，是一个可移植的、可扩展的开源平台，用于管理容器化的工作负载和服务。Kubernetes 原生服务发现手段主要包括：DNS 和 API Server。DNS 服务发现是一种服务地址的通用方案，不过对于相对复杂 Dubbo 元数据而言，这种服务发现机制或许无法直接被 Dubbo Registry SPI 适配。相反，API Server 所支持相对更便利，毕竟 [Spring Cloud Kubernetes](https://github.com/spring-cloud/spring-cloud-kubernetes) 同样基于此机制实现，并已在生产环境得到验证。换言之，只要 Dubbo 支持 Spring Cloud 服务注册与发现模型，那么基于 Kubernetes API Server 的支持也能实现。\n\n\n\n### 兼容 Dubbo 传统服务注册与发现模型\n\n所谓兼容 Dubbo 传统服务注册与发现模型，包含两层含义：\n\n- 基于 Dubbo Registry SPI 同时支持 Spring Cloud 和 Kubernetes 服务注册与发现模型\n- 传统和新的 Dubbo 服务注册与发现模型之间能够相互发现\n\n\n\n## 如何精简 Dubbo URL 元数据\n\nDubbo 从 2.7.0 开始增加了简化 URL 元数据的特性，被“简化”的数据存放至[元数据中心](http://dubbo.apache.org/en-us/docs/user/references/metadata/introduction.html)。由于 Dubbo 传统服务注册与发现模型并未减少 Dubbo 服务 URL 注册数量。因此，精简后的 URL 并未明显地减少注册中心所承受的压力。同时，Dubbo URL 元数据精简模式存在一定的限制，即所有的 Dubbo Provider 节点必须是无状态的，每个节点中的 URL 元信息均是一致的，现实中，这个要求非常难以保证，尤其在同一 Provider 节点存在不同的版本或配置的情况下。综上所述，Dubbo URL 元数据需要进一步精简，至少压力应该避免聚集在注册中心之上。\n\n\n\n\n\n# 架构设计\n\n架构上，Dubbo 服务自省不仅要解决上述挑战，而且实际场景则更为复杂，因此，架构细节也将循序渐进地展开讨论，整体架构可由以下子架构组成：\n\n- 服务注册与发现架构\n- 元数据服务架构\n- 事件驱动架构\n\n\n\n## 服务注册与发现架构\n\nDubbo 服务自省首要需求是减轻注册中心的承载的压力，同时，以应用为粒度的服务注册与发现模型不但能够最大化的减少 Dubbo 服务元信息注册数量，而且还能支持 Spring Cloud 和 Kubernetes 环境，可谓是一举两得，架构图如下所示：\n\n\n\n![image.png](/img/assets/dubbo/2020-05-11/2.png)\n\n**(图 2）**\n\n\n\n### 注册实体\n\n图中所示，从 Provider 和 Consumer 向注册中心注册的实体不再是 Dubbo URL，而是服务实例（Service Instance），一个服务实例代表一个 Provider 或 Consumer Dubbo 应用进程。服务实例属性包括：\n\n- 服务名（Service Name）：该名称必须在注册中心全局唯一\n\n> 注：名称规则架构上不做约束，不过不同注册中心的规则存在差异\n\n- 主机地址（Host/IP）：能够被解析的主机名或者 TCP IP 地址\n- 服务端口（Port）：应用进程所暴露的 Dubbo 协议端口，如 Dubbo 默认端口 20880\n\n> 注：如果应用进程暴露多个 Dubbo 协议端口，如 dubbo 和 rest，那么，服务端口随机挑选其一，架构上不强制检验端口是否可用\n\n- 元数据（Metadata）：服务实例的附加信息，用于存储 Dubbo 元信息，类似于通讯协议头或附件\n- 激活状态（Enabled）：用于标记当前实例是否对外提供服务\n\n\n\n上述**服务实例模型的支持依赖于注册中心的实现**。换言之，并非所有注册中心实现满足服务自省架构的要求。\n\n\n\n### 注册中心\n\n除了满足服务实例模型的要求之外，注册中心还得具备以下能力：\n\n- 服务实例变化通知（Notification）：如上图步骤 4 所示，当 Consumer 订阅的 Provider 的服务实例发生变化时，注册中心能够实时地通知 Consumer\n- 心跳检测（Heartbeats）：注册中心能够检测失效的服务实例，并且合理地移除它们\n\n\n\n业界主流的注册中心中满足上述要求的有：\n\n- Apache [Zookeeper](https://zookeeper.apache.org/)\n- HashiCorp [Consul](https://www.consul.io/)\n- Netflix [Eureka](https://github.com/netflix/Eureka)\n- Alibaba [Nacos](https://nacos.io/)\n- Kubernetes API Server\n\n\n\n总之，**Spring Cloud 与** **Kubernetes** **注册中心均符合服务自省对注册中心的要求**。不过，在 Dubbo 传统 RPC 使用场景中，Provider 和 Consumer 关注的是 Dubbo 服务接口，而非 Service 或服务实例。假设需要将现有的 Dubbo 应用迁移至服务自省架构，Provider 和 Consumer 做大量的代码调整是不现实的。理想的情况下，两端实现代码均无变化，仅修改少量配置，就能达到迁移的效果。那么，Dubbo 服务接口是如何与 Service 进行映射的呢？\n\n\n\n### Dubbo 服务与 Service 映射\n\n前文曾讨论，单个 Dubbo Service 能够发布多个 Dubbo 服务，所以，Dubbo 服务与 Service 的数量关系是 N 对 1。不过，Dubbo 服务与 Dubbo Service 之间并不存在强绑定关系，换言之，某个 Dubbo 服务也能部署在多个 Dubbo Services 中，因此，Dubbo 服务与 Service 数量关系是 N 对 M（N, M >= 1），如下图所示：\n\n\n\n![image.png](/img/assets/dubbo/2020-05-11/3.png)\n\n**(图 3）**\n\n\n\n上图中 P1 Service 到 P3 Service 为 Dubbo Service，com.acme.Interface1 到 com.acme.InterfaceN 则为 Dubbo 服务接口全称限定名（QFN）。值得注意的是，Dubbo 服务的 Java 接口（interface）允许不同的版本（version）或分组（group），所以仅凭 Java 接口无法唯一标识某个 Dubbo 服务，还需要增加通讯协议（protocol）方可，映射关系更新如下：\n\n![image.png](/img/assets/dubbo/2020-05-11/4.png)\n\n**(图 4）**\n\n\n\nDubbo 服务 ID 字符表达模式为： `${protocol}:${interface}:${version}:${group}` , 其中，版本（version）或分组（group）是可选的。当 Dubbo Consumer 订阅 Dubbo 服务时，构建对应 ID，通过这个 ID 来查询 Dubbo Provider 的 Service 名称列表。\n\n\n\n由于 Dubbo 服务与 Service 的映射关系取决于业务场景，架构层面无从预判。因此，这种映射关系只能在 Dubbo 服务暴露时（运行时）才能确定，否则，Dubbo 服务能被多个 Consumer 应用订阅时，Consumer 无法定位 Provider Service 名称，进而无法完成服务发现。同时，映射关系的数据通常采用配置的方式来存储，服务自省提供两种配置实现，即 “中心化映射配置” 和 “本地化映射配置”。\n\n\n\n\n\n#### 中心化映射配置 \n\n明显地，注册中心来扮演动态映射配置的角色并不适合，不然，Dubbo Service 与映射关系在注册中心是平级的，无论在理解上，还是设计上是混乱的。结合 Dubbo 现有基础设施分析，这个存储设施可由 Dubbo 配置中心承担。\n\n其中 Dubbo 2.7.5 动态配置 API（`DynamicConfiguration` ）支持二级结构，即：group 和 key，其中，group 存储 Dubbo 服务 ID，而 key 则关联对应的 Dubbo Service 名称，对应的 \"图 4” 的数据结构则是：\n\n![image.png](/img/assets/dubbo/2020-05-11/5.png)\n\n**(图 5）**\n\n\n\n如此设计的原因如下：\n\n1. 获取 Dubbo 服务对应 Services\n\n利用 `DynamicConfiguration#getConfigKeys(String group)` 方法，能够轻松地通过 Dubbo 服务 ID 获取其发布的所有 Dubbo Services，结合服务发现接口获取服务所部署的 Service 实例集合，最终转化为  Dubbo `URL` 列表。\n\n\n\n1. 避免 Dubbo Services 配置相互覆盖\n\n以 Dubbo 服务 ID `dubbo:com.acme.Interface1:default` 为例，它的提供者 Dubbo Services 分别：P1 Service 和 P2 Service。假设配置 Group 为 \"default\"（任意名字均可）， Key 为 \"dubbo:com.acme.Interface1:default\"，而内容则是 Dubbo Service 名称的话。当 P1 Service 和 P2 Service 同时启动时，无论哪个 Services 最后完成 Dubbo 服务暴露，那么，该配置内容必然是二选其一，无论配置中心是否支持原子操作。即使配置中心支持内容追加的特性，由于两个 Service 服务实例过程不确定，配置内容可能会出现重复，如：“P1 Service,P2 Service,P1 Service”。\n\n\n\n1. 获取 Dubbo 服务发布的 timestamp\n\n\n\n\n\n\n\n##### 配置中心潜在的压力 \n\n假设当 P1 Service 存在 5 个服务实例，当 Dubbo 服务 `dubbo:com.acme.Interface1:default`（ID）发布时，配置所关联的 key 就是当前 Dubbo Service 名称，即（P1 Service），而内容则是最后发布该 Dubbo 服务的时间戳（timestamp）。当服务实例越多时，配置中心和网络传输所承受的写入压力也就越大。当然架构设计上，服务自省也希望避免重复推送配置，比如在 `DynamicConfiguration` API 增加类似于 `publishConfigIfAbsent` 这样的方法，不过目前大多数配置中心产品（如：Nacos、Consul）不支持这样的操作，所以未来服务自省架构会有针对性的提供支持（如：Zookeeper）。\n\n\n\n##### 注册中心作为配置中心\n\n由于服务自省架构必须依赖注册中心，同时动态映射配置又依赖配置中心的话，应用的架构复杂度和维护成本均有所提升，不过 Apache Dubbo 所支持的部分注册中心也可作为配置中心使用，情况如下所示：\n\n| 基础软件                                            | 注册中心 | 配置中心 |\n| --------------------------------------------------- | -------- | -------- |\n| Apache [Zookeeper](https://zookeeper.apache.org/)   | ✅        | ✅        |\n| HashiCorp [Consul](https://www.consul.io/)          | ✅        | ✅        |\n| Alibaba [Nacos](https://nacos.io/)                  | ✅        | ✅        |\n| Netflix [Eureka](https://github.com/netflix/Eureka) | ✅        | ❌        |\n| Kubernetes API Server                               | ✅        | ❌        |\n\n其中，[Zookeeper](https://zookeeper.apache.org/)、[Consul](https://www.consul.io/) 和 [Nacos](https://nacos.io/) 是目前业界流行的注册中心，这对于大多数选择开源产品的应用无疑是一个福音。\n\n\n\n\n\n\n\n#### 本地化映射配置\n\n如果开发人员认为配置中心的引入增加了架构的复杂性，那么，静态映射配置或许是一种解决方案。\n\n>  该特性并未在最新 Dubbo 2.7.6 全面发布，部分特性已在 [Dubbo Spring Cloud](https://github.com/alibaba/spring-cloud-alibaba/wiki/Dubbo-Spring-Cloud) 中发布\n\n\n\n##### 接口映射配置\n\n在 Dubbo 传统的编程模型中， 常以 Java 注解 `@Reference` 或 XML 元素 `` 订阅目标 Dubbo 服务。服务自省架构在此基础上增加 `service` 属性的映射一个或多个 Dubbo Service 名称，如：\n\n```\n<reference services=\"P1 Service,P2 Service\" interface=\"com.acme.Interface1\" />\n```\n\n或\n\n```\n@Reference(services=\"P1 Service,P2 Service\") \nprivate com.acme.Interface1 interface1;\n```\n\n如此配置后，Dubbo 服务 `com.acme.Interface1` 将向 `p1-service` 和 `p2-service` 订阅服务。如果开发人员认为这种方式会侵入到代码，服务自省还提供外部化配置方式配置映射。\n\n\n\n##### 外部化映射配置\n\n服务自省架构支持外部化配置的方式声明“Dubbo 服务与 Service 映射”，配置格式为 `Properties` ，以图 4 为例，内容如下：\n\n```\ndubbo\\:com.acme.Interface1\\:default = P1 Service,P2 Service\nthirft\\:com.acme.InterfaceX = P1 Service,P3 Service\nrest\\:com.acme.interfaceN = P1 Service\n```\n\n\n\n##### 应用级别映射配置\n\n除此之外，[Dubbo Spring Cloud](https://github.com/alibaba/spring-cloud-alibaba/wiki/Dubbo-Spring-Cloud) 提供应用级别的 Dubbo 服务映射配置，即 `dubbo.cloud.subscribed-services` ，例如：\n\n```\ndubbo:\n    cloud:\n    subscribed-services: P1 Service,P3 Service\n```\n\n\n\n总之，无论是映射配置的方式是中心化还是本地化，服务 Consumer 依赖这些数据来定位 Dubbo Provider Services，再通过服务发现 API 结合 Service 名称（列表）获取服务实例集合，为合成 Dubbo URL 做准备：\n\n![image.png](/img/assets/dubbo/2020-05-11/6.png)\n\n**(图 6）**\n\n\n\n不过，映射关系并非是一种强约束，Dubbo Provider 的服务是否可用的检验方法是探测目标 Dubbo Service 是否存在，并需确认订阅的 Dubbo 服务在目标 Services 是否真实暴露，因此，服务自省引入了 Dubbo 元数据服务架构，来完成 Dubbo 服务 URL 的存储。\n\n\n\n\n\n## 元数据服务架构\n\nDubbo 元数据服务是一个常规的 Dubbo 服务，为服务订阅端提供 Dubbo 元数据的服务目录，类似于 WebServices 中的 [WDSL](https://en.wikipedia.org/wiki/Web_Services_Description_Language) 或 REST 中的 [HATEOAS](https://en.wikipedia.org/wiki/HATEOAS)，帮助 Dubbo Consumer 获取订阅的 Dubbo 服务的 URL 列表。元数据服务架构无法独立于服务注册与发现架构而存在，下面通过“整体架构”的讨论，了解两者之间的关系。\n\n\n\n\n\n### 整体架构\n\n架构上，无论 Dubbo Service 属于 Provider 还是 Consumer，甚至是两者的混合，每个 Dubbo （Service）服务实例有且仅有一个 Dubbo 元数据服务。换言之，Dubbo Service 不存在纯粹的 Consumer，即使它不暴露任何业务服务，那么它也可能是 Dubbo 运维平台（如 Dubbo Admin）的 Provider。不过出于行文的习惯，Consumer 仍旧被定义为 Dubbo 服务消费者（应用）。由于每个 Dubbo Service 均发布自身的 Dubbo 元数据服务，那么，架构不会为不同的 Dubbo Service 设计独立的元数据服务接口（Java）。换言之，所有的 Dubbo Service 元数据服务接口是统一的，命名为 `MetadataService` 。\n\n\n\n#### 微观架构\n\n从 Dubbo 服务（URL）注册与发现的视角， `MetadataService` 扮演着传统 Dubbo 注册中心的角色。综合服务注册与发现架构（Dubbo Service 级别），微观架构如下图所示：\n\n\n\n![image.png](/img/assets/dubbo/2020-05-11/7.png)\n\n**(图 7）**\n\n**\n**\n\n对于 Provider（服务提供者）而言，Dubbo 应用服务暴露与传统方式无异，而 `MetadataService` 的暴露时机必须在它们完成后，同时， `MetadataService` 需要收集这些 Dubbo 服务的 URL（存储细节将在“元数据服务存储模式“ 小节讨论）。假设某个 Provider 的 Dubbo 应用服务暴露数量为 N，那么，它所有的 Dubbo 服务暴露数量为 N + 1。\n\n\n\n对于 Consumer（服务消费者）而言，获 Dubbo 应用服务订阅 URL 列表后，Dubbo 服务调用的方式与传统方式是相同的。不过在此之前，Consumer 需要通过 `MetadataService` 合成订阅 Dubbo 服务的 URL。该过程之所以称之为“合成”，而非“获取，是因为一次 `MetadataService` 服务调用仅在其 Provider 中的一台服务实例上执行，而该 Provider 可能部署了 N 个服务实例。具体“合成”的细节需要结合“宏观架构”来说明。\n\n\n\n#### 宏观架构\n\n元数据服务的宏观架构依赖于服务注册与发现架构，如下图所示：\n\n![image.png](/img/assets/dubbo/2020-05-11/8.png)\n\n**(图 8）**\n\n\n\n图 8 中 p 和 c 分别代表 Provider 和 Consumer 的执行动作，后面紧跟的数字表示动作的次序，从 0 开始计数。执行动作是串行的，并属于 Fast-Fail 设计，如果前阶段执行失败，后续动作将不会发生。之所以如此安排是为了确保 `MetadataService` 能够暴露和消费。首先从 Provider 执行流程开始说明。\n\n\n\n##### Provider 执行流程\n\n- p0：发布所有的 Dubbo 应用服务，声明和定义方式与传统方式完全相同。\n- p1：暴露 `MetadataService` ，该步骤完全由框架自行处理，无论是否 p0 是否暴露 Dubbo 服务\n- p2：在服务实例注册之前， 框架将触发并处理事件（Event），将 `MetadataService` 的元数据先同步到服务实例（Service Instance）的元数据。随后，执行服务实例注册\n- p3：建立所有的 Dubbo 应用服务与当前 Dubbo Service 名称的映射，并同步到配置源（抽象）\n\n\n\n##### Consumer 执行流程\n\n- c0：注册当前 Dubbo Service 的服务实例，可选步骤，架构允许 Consumer 不进行服务注册\n- c1：通过订阅 Dubbo 服务元信息查找配置源，获取对应 Dubbo Services 名称（列表）\n- c2：利用已有 Dubbo Service 名称（可能存在多个），通过服务发现 API 获取 Provider 服务实例集合。假设 Service 名称 P，服务实例数量为 N\n- c3：\n\n1. 1. 随机选择 Provider 一台服务实例 Px，从中获取 `MetadataService` 的元数据\n   2. 将元数据组装 `MetadataService` Dubbo 调用客户端（代理）\n   3. 发起 `MetadataService` Dubbo 调用，获取该服务实例 Px 所暴露的 Dubbo 应用服务 URL 列表\n   4. 从 Dubbo 应用服务 URL 列表过滤出当前订阅 Dubbo 应用服务的 URL\n   5. 理论上，步骤 c 和 d 还需要执行 N-1 次才能获取 P 所有服务实例的 Dubbo URL 列表。为了减少调用次数，步骤 d 的结果作为模板，克隆其他 N-1 台服务实例 URL 列表 \n   6. 将所有订阅 Dubbo 应用服务的 URL 同步到 Dubbo 客户端（与传统方式是相同的）\n\n- c4：发起 Dubbo 应用服务调用（与传统方式是相同的）\n\n\n\n不难看出，上述架构以及流程结合了“服务注册与发现”与“元数据服务”双架构，步骤之间会触发相关 Dubbo 事件，如“服务实例注册前事件”等。换言之，三种架构综合体也就是服务自省架构。\n\n\n\n至此，关于 Dubbo 服务自省架构设计方面，还存在一些细节亟待说明，比如：\n\n1. 不同的 Dubbo Service 的 `MetadataService` 怎样体现差异呢？\n2. `MetadataService` 作为一个常规的 Dubbo 服务，它的注册元信息存放在何处？\n3. `MetadataService` 作为服务目录，它管理的 Dubbo 应用服务 URL 是如何存储的？\n4. 在 Consumer 执行流程的 c3.e 中，克隆 N - 1 条 URL 的前提是该 Provider 的所有服务实例均部署了相同 Dubbo 应用服务。如果 Provider 处于升级的部署过程，同一 Dubbo 应用服务接口在不同的服务实例上存在差异，那么该服务的 URL 应该如何获取？\n5. 除了 Dubbo 服务 URL 发现之外，元数据服务还支持哪些元数据类型呢？\n\n\n\n\n\n### 元数据服务 Metadata\n\n元数据服务 Metadata，称之为“元数据服务的元数据”，主要包括：\n\n- inteface：Dubbo 元数据服务所暴露的接口，即 `MetadataService` \n- serviceName : 当前 `MetadataService` 所部署的 Dubbo Service 名称，作为 `MetadataService` 分组信息\n- group：当前 `MetadataService` 分组，数据使用 serviceName\n- version：当前 `MetadataService` 的版本，版本号通常在接口层面声明，不同的 Dubbo 发行版本 version 可能相同，比如 Dubbo 2.7.5 和 2.7.6 中的 version 均为 1.0.0。理论上，version 版本越高，支持元信息类型更丰富\n- protocol： `MetadataService` 所暴露协议，为了确保 Provider 和 Consumer 通讯兼容性，默认协议为：“dubbo”，也可以支持其他协议。\n- port：协议所使用的网络端口\n- host：当前 `MetadataService` 所在的服务实例主机或 IP\n- params：当前 `MetadataService` 暴露后 URL 中的参数信息\n\n\n\n不难得出，凭借以上元数据服务的 Metadata，可将元数据服务的 Dubbo 服务 ID 确定，辅助 Provider 服务暴露和 Consumer 服务订阅 `MetadataService` 。不过对于 Provider，这些元信息都是已知的，而对 Consumer 而言，它们直接能获取的元信息仅有：\n\n- serviceName：通过“Dubbo 接口与 Service 映射”关系，可得到 Provider Service 名称\n- interface：即 `MetadataService` ，因为 Provider 和 Consumer 公用 `MetadataService` 接口\n- group：即 serviceName\n\n\n\n不过 Consumer 合成 `MetadataService` Dubbo URL 还需获取 version、host、port、protocol 以及 params：\n\n- version：尽管 `MetadataService` 接口是统一接口，然而 Provider 和 Consumer 可能引入的 Dubbo 版本不同，从而它们使用的 `MetadataService` version 也会不同，所以这个信息需要 Provider 在暴露`MetadataService` 时，同步到服务实例的 Metadata 中，方便 Consumer 从 Metadata 中获取\n- host：由于 Consumer 已得到 serviceName，可通过服务发现 API 获取服务实例对象，该对象包含 host 属性，直接被 Consumer 获取即可。\n- port：与 version 类似，从 Provider 服务实例中的 Metadata 中获取\n- params：同上\n\n\n\n通过元数据服务 Metadata 的描述，解释了不同 Dubbo Services 是怎样体现差异性的，并且说明了 `MetadataService` 元信息的存储介质，这也就是服务自省架构为什么强依赖支持 Metadata 的注册中心的原因。下个小节将讨论 `MetadataService` 所存储 Dubbo 应用服务 URL 存放在何处。\n\n\n\n\n\n### 元数据服务存储模式\n\nDubbo 2.7.5 在引入 `MetadataService` 的同时，也为其设计了两种存储方式，适用于不同的场景，即“本地存储模式”和“远程存储模式”。其中，本地存储模式是默认选项。\n\n\n\n#### 元数据服务本地存储模式\n\n本地存储模式又称之为内存存储模式（In-Memory），如 Dubbo 应用服务发现和注册场景中，暴露和订阅的 URL 直接存储在内存中。架构上，本地存储模式的 `MetadataService` 相当于去中心化的 Dubbo 应用服务的注册中心。\n\n \n\n#### 元数据服务远程存储模式\n\n远程存储模式，与去中心化的本地存储模式相反，采用 Dubbo 元数据中心来管理 Dubbo 元信息，又称之为元中心化存储模式（Metadata Center)。\n\n\n\n\n\n#### 选择存储模式\n\n为了减少负载压力和维护成本，服务自省中的元数据服务推荐使用**“****本****地存储模式”**。\n\n\n\n回顾前文“Consumer 执行流程”中的步骤 c3.e，为了减少 `MetadataService` 调用次数，服务自省将第一次的调用结果作为模板，再结合其他 N-1 服务实例的元信息，合成完整的 N 台服务实例的 Dubbo 元信息。假设，Dubbo Service 服务实例中部署的 Dubbo 服务数量和内容不同，那么，c3.e 的执行步骤是存在问题的。因此，服务自省引入“**Dubbo 服务修订版本**”的机制来解决不对等部署的问题。\n\n\n\n尽管“Dubbo 服务修订版本”机制能够介绍 `MetadataService` 整体消费次数，然而当新修订版本的服务实例过少，并且 Consumer 过多时，如新的版本 Provider 应用分批部署，每批的服务实例为 1 台，而其 Consumer 服务实例成千上万。为了确保这类场景的稳定性，Provider 和 Consumer 的 `MetadataService` 可选择“**远程存储模式**”，避免消费热点的发生。\n\n\n\n\n\n### Dubbo 服务修订版本 \n\n当业务出现变化时，Dubbo Service 的 Dubbo 服务也会随之升级。通常，Provider 先行升级，Consumer 随后跟进。\n\n\n\n考虑以下场景，Provider “P1” 线上已发布 interface 为 `com.acme.Interface1`，group 为 `group` , version 为 `v1` ，即 Dubbo 服务 ID 为：`dubbo:com.acme.Interface1:v1:default` 。P1 可能出现升级的情况有：\n\n\n\n1. Dubbo 服务 interface 升级\n\n由于 Dubbo 基于 Java 接口来暴露服务，同时 Java 接口通常在 Dubbo 微服务中又是唯一的。如果 interface 的全类名调整的话，那么，相当于 `com.acme.Interface1` 做下线处理，Consumer 将无法消费到该 Dubbo 服务，这种情况不予考虑。如果是 Provider 新增服务接口的话，那么 `com.acme.Interface1` 则并没有变化，也无需考虑。所以，有且仅有一种情况考虑，即“**Dubbo interface 方法声明升级**”，包括：\n\n- 增加服务方法\n- 删除服务方法\n- 修改方法签名\n\n\n\n1. Dubbo 服务 group、version 和 protocol 升级\n\n假设 P1 在升级过程中，新的服务实例部署仅存在调整 group 后的 Dubbo 服务，如 `dubbo:com.acme.Interface1:v1:test` ，那么这种升级就是不兼容升级，在新老交替过程中，Consumer 仅能消费到老版本的 Dubbo 服务。当新版本完全部署完成后，Consumer 将无法正常服务调用。如果，新版本中 P1 同时部署了 `dubbo:com.acme.Interface1:v1:default`\n\n 和 `dubbo:com.acme.Interface1:v1:test` 的话，相当于 group 并无变化。同理，version 和 protocol 变化，相当于 Dubbo 服务 ID 变化，**这类情况无需处理**。\n\n\n\n1. Dubbo 服务元数据升级\n\n这是一种比较特殊的升级方法，即 Provider 所有服务实例 Dubbo 服务 ID 相同，然而 Dubbo 服务的参数在不同版本服务实例存在差异，假设 Dubbo Service P1 部署 5 台服务，其中 3 台服务实例设置 timeout 为 1000 ms，其余 2 台 timeout 为 3000 ms。换言之，P1 拥有两个版本（状态）的 `MetadataService` 。\n\n\n\n综上所述，无论是 Dubbo interface 方法声明升级，还是 Dubbo 服务元数据升级，均可认为是 Dubbo 服务升级的因子，这些因子所计算出来的数值称之为“Dubbo 服务修订版本”，服务自省架构将其命名为“**revision**”。架构设设计上，当 Dubbo Service 增加或删除服务方法、修改方法签名以及调整 Dubbo 服务元数据，revision 也会随之变化，revision 数据将存放在其 Dubbo 服务实例的 metadata 中。当 Consumer 订阅 Provider Dubbo 服务元信息时，`MetadataService` 远程调用的次数取决于服务实例列表中出现 revision 的个数，整体执行流程如下图所示：\n\n![image.png](/img/assets/dubbo/2020-05-11/9.png)\n\n**(图 9）**\n\n\n\n1. Consumer 通过服务发现 API 向注册中心获取 Provider 服务实例列表\n2. 注册中心返回 6 台服务实例，其中 revision 为 1 的服务实例为 Instance 1 到 3, revision 为 2 的服务实例是 Instance 4 和 Instance 5，revision 为 3 的服务实例仅有 Instance 6\n3. Consumer 在这 6 台服务实例中随机选择一台，如图中 Instance 3\n4. Consumer 向 Instance 3 发起 `MetadataService` 的远程调用，获得 Dubbo URL 列表，并建立 revision 为 1 的 URL 列表缓存，用 cache = { 1:urls(r1) } 表示\n5. （重复步骤 4）Consumer 再从剩余的 5 台服务实例中随机选择一台，如图中的 Instance 5，由于 Instance 5 与 Instance 3 的 revision 分为为 2 和 1，此时缓存 cache = { 1:urls(r1) } 未命中，所以 Consumer 将再次发起远程调用，获取新的 Dubbo URL 列表，并更新缓存，即 cache = { 1:urls(r1) , 2:urls(r2) }\n6. （重复步骤 4）Consumer 再从剩余的 4 台服务实例中随机选择一台，假设服务实例是 Instance 6，由于此时 revision 为3，所以缓存 cache = { 1:urls(r1) , 2:urls(r2) } 再次未命中，再次发起远程调用，并更新缓存 cache = { 1:urls(r1) , 2:urls(r2) , 3:urls(r3) }\n7. （重复步骤 4）由于缓存 cache = { 1:urls(r1) , 2:urls(r2) , 3:urls(r3) } 已覆盖三个 revision 场景，如果该步骤选择服务实例落在 revision 为 1 的子集中，只需克隆 urls(r1)，并根据具体服务实例替换部分 host 和 port 等少量元信息即可，组成成新的 Dubbo URL 列表，依次类推，计算直到剩余服务实例为 0。\n\n\n\n大多数情况，revision 的数量不会超过 2，换言之，Consumer 发起 `MetadataService` 的远程调用不会超过 2次。无论 revision 数量的大小，架构能够保证获取 Dubbo 元信息的正确性。\n\n \n\n当然  `MetadataService` 并非仅支持 Dubbo URL 元数据，还有其他类型的支持。\n\n\n\n\n\n### 元数据类型\n\n架构上，元数据服务（`MetadataService`）未来将逐步替代 Dubbo 2.7.0 [元数据中心](http://dubbo.apache.org/zh-cn/docs/user/references/metadata/introduction.html)，并随着 Dubbo 版本的更迭，所支持的元数据类型也将有所变化，比如 Dubbo 2.7.5 元数据服务支持的类型包括：\n\n- Dubbo 暴露的服务 URL 列表\n- Dubbo 订阅的服务 URL 列表\n- Dubbo 服务定义\n\n\n\n#### Dubbo 暴露的服务 URL 列表\n\n当前 Dubbo Service 暴露或发布 Dubbo 服务 URL 集合，如：[ `dubbo://192.168.1.2:20880/com.acme.Interface1?group=default&version=v1` , `thirft://192.168.1.2:20881/com.acme.InterfaceX` , `rest://192.168.1.2:20882/com.acme.interfaceN` ]\n\n\n\n#### Dubbo 订阅的服务 URL 列表\n\n当前 Dubbo Service 所有订阅的 Dubbo 服务 URL 集合，该元数据主要被 Dubbo 运维平台来收集。\n\n\n\n#### Dubbo 服务定义\n\nDubbo 服务提供方（Provider）在服务暴露的过程中，将元信息以 JSON 的格式同步到注册中心，包括服务配置的全部参数，以及服务的方法信息（方法名，入参出参的格式）。在服务自省引入之前，该元数据被 Dubbo 2.7.0 [元数据中心](http://dubbo.apache.org/zh-cn/docs/user/references/metadata/introduction.html) 存储，如：\n\n```\n{\n \"parameters\": {\n  \"side\": \"provider\",\n  \"methods\": \"sayHello\",\n  \"dubbo\": \"2.0.2\",\n  \"threads\": \"100\",\n  \"interface\": \"org.apache.dubbo.samples.metadatareport.configcenter.api.AnnotationService\",\n  \"threadpool\": \"fixed\",\n  \"version\": \"1.1.1\",\n  \"generic\": \"false\",\n  \"revision\": \"1.1.1\",\n  \"valid\": \"true\",\n  \"application\": \"metadatareport-configcenter-provider\",\n  \"default.timeout\": \"5000\",\n  \"group\": \"d-test\",\n  \"anyhost\": \"true\"\n },\n \"canonicalName\": \"org.apache.dubbo.samples.metadatareport.configcenter.api.AnnotationService\",\n \"codeSource\": \"file:/../dubbo-samples/dubbo-samples-metadata-report/dubbo-samples-metadata-report-configcenter/target/classes/\",\n \"methods\": [{\n  \"name\": \"sayHello\",\n  \"parameterTypes\": [\"java.lang.String\"],\n  \"returnType\": \"java.lang.String\"\n }],\n \"types\": [{\n  \"type\": \"java.lang.String\",\n  \"properties\": {\n   \"value\": {\n    \"type\": \"char[]\"\n   },\n   \"hash\": {\n    \"type\": \"int\"\n   }\n  }\n }, {\n  \"type\": \"int\"\n }, {\n  \"type\": \"char\"\n }]\n}\n```\n\n\n\n#### 更多元数据类型支持\n\n在架构上，元数据服务（`MetadataService`）所支持元数据类型是不限制的，如下图所示：\n\n![image.png](/img/assets/dubbo/2020-05-11/10.png)\n\n**(图 10）**\n\n\n\n除上文曾讨论的三种元数据类型，还包括“Dubbo 服务 REST 元信息” 和 “其他元信息”。其中，Dubbo 服务 REST 元信息包含 Dubbo 服务 与 REST 映射信息，可用于 Dubbo 服务网关，而其他元信息可能包括 Dubbo 服务 JavaDoc 元信息，可用于 Dubbo API 文档。\n\n\n\n\n\n### 元数据服务升级\n\n考虑到 Dubbo Provider 和 Consumer 可能依赖不同发行版本的 `MetadataService` ，因此，Provider 提供的和 Consumer 所需要的元数据类型并不对等，如 Provider 使用 Dubbo 版本为 2.7.5，该发行版本仅支持“Dubbo 暴露的服务 URL 列表”，“Dubbo 订阅的服务 URL 列表”和“Dubbo 服务定义”，这三种元数据分别来源于接口的三个方法。当 Consumer 使用了更高的 Dubbo 版本，并需要获取“Dubbo 服务 REST 元信息”时，自然无法从 Provider 端获取。假设 `MetadataService` 为其新增一个方法，那么，当 Consumer 发起调用时，那么这个调用自然会失败。即使两端使用的版本相同，那么 Provider 仍有可能选择性支持特定的元数据类型。为了确保元数据接口的兼容性，`MetadataService` 应具备元数据类型支持的判断。如此设计，`MetadataService` 在元数据类型上支持更具有弹性。\n\n\n\n\n\n## 事件驱动架构\n\n相较于传统的 Dubbo 架构，服务自省架构的执行流程更为复杂，执行动作之间的关联非常紧密，如 Dubbo Service 服务实例注册前需要完成 Dubbo 服务 revision 的计算，并将其添加至服务实例的 metadata 中。又如当 Dubbo Service 服务实例出现变化时，Consumer 元数据需要重新计算。这些动作被 “事件”（`Event`）驱动，驱动者被定义为“事件分发器”（ `EventDispatcher` ），而动作的处理则由“事件监听器”（`EventListener`）执行，三者均为 “**Dubbo 事件**\"的核心组件，同样由 Dubbo 2.7.5 引入。不过，Dubbo 事件是相对独立的架构，不过被服务自省中的“服务注册与发现架构”和“元数据服务架构”依赖。\n\n\n\n\n\n### Dubbo 内建事件\n\nDubbo 内建事件可归纳为以下类型：\n\n- Dubbo 服务类型事件\n- Dubbo Service 类型事件\n- Dubbo 服务实例类型事件\n- Dubbo 服务注册和发现类型事件\n\n\n\n\n\n#### Dubbo 服务类型事件\n\n| 事件类型                          | 事件触发时机              |\n| --------------------------------- | ------------------------- |\n| `ServiceConfigExportedEvent`      | 当 Dubbo 服务暴露完成时   |\n| `ServiceConfigUnexportedEvent`    | 当 Dubbo 服务下线后       |\n| `ReferenceConfigInitializedEvent` | 当 Dubbo 服务引用初始化后 |\n| `ReferenceConfigDestroyedEvent`   | 当 Dubbo 服务引用销毁后   |\n\n\n\n\n\n#### Dubbo Service 类型事件\n\n| 事件类型                             | 事件触发时机                 |\n| ------------------------------------ | ---------------------------- |\n| `DubboShutdownHookRegisteredEvent`   | 当 Dubbo ShutdownHook 注册后 |\n| `DubboShutdownHookUnregisteredEvent` | 当 Dubbo ShutdownHook 注销后 |\n| `DubboServiceDestroyedEvent`         | 当 Dubbo 进程销毁后          |\n\n\n\n\n\n#### Dubbo 服务实例类型事件\n\n| 事件类型                              | 事件触发时机                                 |\n| ------------------------------------- | -------------------------------------------- |\n| `ServiceInstancePreRegisteredEvent`   | 当 Dubbo 服务实例注册前                      |\n| `ServiceInstanceRegisteredEvent`      | 当 Dubbo 服务实例注册后                      |\n| `ServiceInstancePreUnregisteredEvent` | 当 Dubbo 服务实例注销前                      |\n| `ServiceInstanceUnregisteredEvent`    | 当 Dubbo 服务实例注销后                      |\n| `ServiceInstancesChangedEvent`        | 当 某个 Dubbo Service 下的服务实例列表变更时 |\n\n\n\n\n\n#### Dubbo 服务注册和发现类型事件\n\n| 事件类型                            | 事件触发时机                          |\n| ----------------------------------- | ------------------------------------- |\n| `ServiceDiscoveryInitializingEvent` | 当 Dubbo 服务注册与发现组件初始化中   |\n| `ServiceDiscoveryInitializedEvent`  | 当 Dubbo 服务注册与发现组件初始化后   |\n| `ServiceDiscoveryExceptionEvent`    | 当 Dubbo 服务注册与发现组件异常发生时 |\n| `ServiceDiscoveryDestroyingEvent`   | 当 Dubbo 服务注册与发现组件销毁中     |\n| `ServiceDiscoveryDestroyedEvent`    | 当 Dubbo 服务注册与发现组件销毁后     |\n\n\n\n\n\n# 结束语\n\n\n\n\n\n# What's the next?"
  },
  {
    "path": "_posts/2020-10-28-《深入理解 Spring Cloud 与实战》序.md",
    "content": "# 《深入理解 Spring Cloud 与实战》序\n\n2017 年，我与作者在国内开源社区相识，那时给人的印象是，他是一名乐于分享并且积极上进的青年。后来，得知他入职阿里巴巴中间件部门，并一同参与了 Spring Cloud 与阿里巴巴以及阿里云的技术整合项目，也就是后来的 Spring Cloud Alibaba。\n\n\n\n作为一名资深开发人员，作者的职业素养具备专业、严谨、热情和强烈的责任心。记得 2018 年 11 月，我们一起参加 Spring One Tour 2018 北京峰会。会后，作者给 Spring Cloud Leader Spencer 提出了不少关于 Spring Cloud 实现上的建议。有趣的是，刚走出会议大厅，作者又接到来自社区贡献者军哥（nepxion.com 项目发起人）的电话，反馈 Spring Cloud Alibaba 孵化版本中的问题。经过技术排查，问题的确存在。数次电话沟通中，作者面漏歉意，并承诺尽快将问题修复。我想，大多数工程师都经历过这种情况。晚间，我们来到 Josh Long（Spring Developer Advocate）下塔的酒店，作者向对方演示 Spring Cloud Alibaba RocketMQ 在本地和阿里云环境的操作。时间不知不觉已是凌晨四点。后来，Josh 录制了相关的视频，分享在 YouTube 上。\n\n\n\n作为一名 Spring Cloud Alibaba 项目负责人，作者除了代码贡献之外，更多的精力投入在社区运营、维护与 Spring 官方沟通上，进过一年多的不懈地努力，2019 月 7 月 24 日，Spring 官方宣布 Spring Cloud Alibaba 毕业，仓库迁移至 Alibaba Github OSS 下。这意味着 Spring Cloud Alibaba 是国内首个进入 Spring 社区的开源项目。\n\n\n\n作为一名技术布道者，作者身体力行，2018 至 2019 年，走遍全国 IT 重镇，分享 Spring Cloud 以及 Spring Cloud Alibaba 等技术。几乎每场分享过后，将自己的技术心得和体会以博文的方式发布。尽管网上已有不少的相关技术文章，然而作者文章的技术深度和广度是有过之而无不及的。同时，作者意识到单凭技术文章很难系统性的探讨技术细节，由此萌生了写书的念头。于是，作者与我讨论写书一些技巧和注意事项。\n\n\n\n写书的过程必然是漫长而曲折的，尤其需要在繁忙的日常工作中整合零散的时间。更棘手的是，写作期间 Spring Cloud 的内核也在不断的变化，这也给内容增添的难度，不得不与时俱进，及时作出调整，时间轴被迫拉长。不过，个人认为这样的等待是值得的，尽管作者是 Spring Cloud Alibaba 的核心开发人员，然而本书并非专题讨论 Spring Cloud Alibaba，并且本书也不是单纯地指导读者如何使用 Spring Cloud，而是从宏观和微观的视角，深入地讨论 Spring Cloud 架构发展，以及各个组件之间的特性和联系。所以，本书不仅适合 Spring Cloud 初探者，也为资深从业人员提供参考，并且本书的行文通俗，章节之间的关联紧密，阅读体验友好，故推荐之。\n\n\n\n正值中美全面竞争的时代，个人认为，彼此在软件技术的差距虽在不断缩小，但绝对值仍旧存在。全球化的进程开阔了我们的视野，然而商业化的运作却使得行业进程迟缓。尽管中国式的互联网是我们常引以为傲的话题，然而大多数场景还停留在技术运用的阶段，中间偶尔出现一些技术创新，还谈不上技术创造。许多从业人员理想的职位是软件架构师或者 CTO，难免过多地关注于软件架构，而忽视了设计思想和代码实现的重要性。我想，任何基础学科的积累都是枯燥和繁琐的，时代的特殊性，导致了行业的局限性。或许本书不是尽善尽美之物，个人希望读者在阅读后，意识到细节的力量。同时，也期盼更多的同仁志士积极参与开源建设，群策群力，促进行业良性循环。\n\n\n\n小马哥（mercyblitz）\n\n2020 年 10 月 28 日"
  },
  {
    "path": "about.html",
    "content": "---\nlayout: page\ntitle: \"About\"\ndescription: \"关于我\"\nheader-img: \"img/about-bg.jpg\"\nmultilingual: true\n---\n\n<!-- Language Selector -->\n<select class=\"sel-lang\" onchange= \"onLanChange(this.options[this.options.selectedIndex].value)\">\n    <option value=\"0\" selected> 中文 Chinese </option>\n    <option value=\"1\"> 英文 English </option>\n</select>\n\n<!-- Chinese Version -->\n<div class=\"zh post-container\">\n    {% capture about_zh %}{% include about/zh.md %}{% endcapture %}\n    {{ about_zh | markdownify }}\n</div>\n\n<!-- English Version -->\n<div class=\"en post-container\">\n    {% capture about_en %}{% include about/en.md %}{% endcapture %}\n    {{ about_en | markdownify }}\n</div>\n\n\n{% if site.disqus_username %}\n<!-- disqus 评论框 start -->\n<div class=\"comment\">\n    <div id=\"disqus_thread\" class=\"disqus-thread\">\n\n    </div>\n</div>\n<!-- disqus 评论框 end -->\n\n<!-- disqus 公共JS代码 start (一个网页只需插入一次) -->\n<script type=\"text/javascript\">\n    /* * * CONFIGURATION VARIABLES * * */\n    var disqus_shortname = \"{{site.disqus_username}}\";\n    var disqus_identifier = \"{{site.disqus_username}}/{{page.url}}\";\n    var disqus_url = \"{{site.url}}{{page.url}}\";\n\n    (function() {\n        var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;\n        dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';\n        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);\n    })();\n</script>\n<!-- disqus 公共JS代码 end -->\n{% endif %}\n"
  },
  {
    "path": "archive.html",
    "content": "---\ntitle: Archive\nlayout: default\ndescription: 归档文章\nheader-img: \"img/tag-bg.jpg\"\n---\n\n<!-- \nCredits: this page shamelessly borrowed a lot from:\nhttps://github.com/kitian616/jekyll-TeXt-theme\n-->\n<!-- Page Header -->\n{% include intro-header.html type='page' short=true %}\n\n<!-- Main Content -->\n<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1\">\n\t\t\t<!-- Tags (as filter) -->\n\t\t\t<div id='tag_cloud' class=\"tags tags-sup js-tags\">\n\t\t\t\t<a class=\"tag-button--all\" data-encode=\"\">\n\t\t\t\t\tShow All\n\t\t\t\t\t<sup>{{site.posts.size}}</sup>\n\t\t\t\t</a>\n\n\t\t\t\t{% capture tags %}\n\t\t\t\t{% for tag in site.tags %}\n\t\t\t\t<a data-sort=\"{{ site.posts.size | minus: tag[1].size | prepend: '0000' | slice: -4, 4 }}\"\n\t\t\t\t\tdata-encode=\"{{ tag[0] | strip | url_encode }}\"\n\t\t\t\t\tclass=\"tag-button\"\n\t\t\t\t\ttitle=\"{{ tag[0] }}\" rel=\"{{ tag[1].size }}\">\n\t\t\t\t\t{{ tag[0] }}\n\t\t\t\t\t<sup>{{tag[1].size}}</sup>\n\t\t\t\t</a>\n\t\t\t\t{% endfor %}\n\t\t\t\t{% endcapture %}\n        {{ tags | split:'</a>' | sort | join:'</a>' }}\n\t\t\t</div>\n\n            <!-- Article List -->\n\t\t\t<div class=\"mini-post-list js-result d-none\">\n\t\t\t{%- assign _sorted_list = site.posts -%}\n\t\t\t{%- assign _sorted_list = _sorted_list | sort: 'date' -%}\n\t\t\t{%- assign _sorted_list = _sorted_list | reverse -%}\n\n\n\t\t\t{%- for _article in _sorted_list -%}\n\t\t\t\t{%- assign _tags = '' -%}\n\t\t\t\t{%- for _tag in _article.tags -%}\n\t\t\t\t\t{%- assign _tag_encode = _tag | strip | url_encode -%}\n\t\t\t\t\t{%- if forloop.last -%}\n\t\t\t\t\t\t{%- assign _tags = _tags | append: _tag_encode -%}\n\t\t\t\t\t{%- else -%}\n\t\t\t\t\t\t{%- assign _tags = _tags | append: _tag_encode | append: ',' -%}\n\t\t\t\t\t{%- endif -%}\n\t\t\t\t{%- endfor -%}\n\n\t\t\t{% comment %} group by year {% endcomment %}\n\t\t\t{%- assign _currentdate = _article.date | date: '%Y' -%}\n\t\t\t{%- if _currentdate != _date -%}\n\t\t\t\t{%- unless forloop.first -%}</section>{%- endunless -%}\n\t\t\t\t<section>\n\t\t\t\t<span class=\"fa listing-seperator\">\n\t\t\t\t\t<span class=\"tag-text\">{{ _currentdate }}</span>\n\t\t\t\t</span>\n\t\t\t\t{%- assign _date = _currentdate -%}\n\t\t\t{%- endif -%}\n\n\t\t\t\t<div class=\"post-preview item\" data-tags=\"{{ _tags }}\">\n\t\t\t\t    <a href=\"{{ _article.url | prepend: site.baseurl }}\">\n\t\t\t\t        <h2 class=\"post-title\">\n                            {{ _article.title }}\n\t\t\t\t        </h2>\n\t\t\t\t        {% if _article.subtitle %}\n\t\t\t\t        <h3 class=\"post-subtitle\">\n\t\t\t\t            {{ _article.subtitle }}\n\t\t\t\t        </h3>\n\t\t\t\t        {% endif %}\n\t\t\t\t    </a>\n\t\t\t\t\t<hr>\n\t\t\t\t</div>\n\t\t\t{% endfor %}\n\t\t</div>\n\t</div>\n</div>\n"
  },
  {
    "path": "books/thinking-in-spring-boot/README.md",
    "content": "# 《Spring Boot 编程思想》\n\n> 谨以此书纪念已故外婆 - 解厚群\n\n本书全名为《Spring Boot 编程思想》，是以 Spring Boot 2.0 为讨论的主线，讨论的范围将涵盖 Spring Boot 1.x 的所有版本，以及所关联的 Spring Framework 版本，致力于：\n\n- 场景分析：掌握技术选型\n- 系统学习：拒绝浅尝辄止\n- 重视规范：了解发展趋势\n- 源码解读：理解设计思想\n- 实战演练：巩固学习成果\n\n\n\n\n## 自序\n\n- [《核心篇》](https://mercyblitz.github.io/books/thinking-in-spring-boot/core/preface/)（[京东](https://item.jd.com/12570242.html)、[当当](http://product.dangdang.com/26922557.html)、[天猫](https://detail.tmall.com/item.htm?id=589445956796) 热售中...）\n- 《运维篇》（即将完稿...)\n- 《Web 篇》(编写中...)\n\n\n\n\n## 扉页\n\n- [内容总览](https://mercyblitz.github.io/books/thinking-in-spring-boot/overview/)\n- [版本范围](https://mercyblitz.github.io/books/thinking-in-spring-boot/version/)\n- [相关约定](https://mercyblitz.github.io/books/thinking-in-spring-boot/conventions/)\n- [配套视频](https://mercyblitz.github.io/books/thinking-in-spring-boot/videos/)\n- [示例工程](https://mercyblitz.github.io/books/thinking-in-spring-boot/samples/)\n- [勘误汇总](https://mercyblitz.github.io/books/thinking-in-spring-boot/revision/)\n- [公益资金流向](https://mercyblitz.github.io/books/thinking-in-spring-boot/donate/)\n\n\n\n\n## [关于我](https://mercyblitz.github.io/books/thinking-in-spring-boot/about/)\n\n“我是谁？”，是个不错的哲学问题。\n\n在江湖上，大家亲切地称我 “小马哥“，我做公益，也做生意；在社区中，我又以 `mercyblitz` 的身份出没在众多开源项目，\"mercy\" 符合我的性格，\"blitz\" 说明我的风格。\n\n承蒙错爱，不少的朋友对我过去的分享称赞有加，然而“千人之诺诺，不如一士之谔谔”，时常又让自己陷入一种迷思，到底是平台的帮衬，还是个人的确禁得起考验。于是我选择隐匿真名，希望能够听到更为真实的声音。尽管在互联网时代，只要稍作功课，个人信息几乎无处遁形。无可讳言，本人的所属公司以及职业头衔必然会形成“舞台效应”，如此一来，不但违背了写书的初衷，而且模糊了讨论的焦点。所以，本书即不会出现这些信息，又不会搞“个人崇拜”。它的价值应该体现在知识的传播，至于它的优劣则由诸君来评判。\n\n\n\n\n## 交流社区\n\n- 微信公众号：次灵均阁\n  \n\n![微信公众号二维码](assets/my_mp_qrcode.jpg)\n\n- [知识星球](https://t.zsxq.com/72rj2rr)：\n  \n\n![小马哥 Java 星球](assets/my_java_planet.png)\n\n- [Github](http://github.com/mercyblitz)：[http://github.com/mercyblitz](http://github.com/mercyblitz)\n\n> 更多个人信息，请使用 [Google 搜索 `mercyblitz`](https://www.google.com/search?q=mercyblitz)\n"
  },
  {
    "path": "books/thinking-in-spring-boot/about.md",
    "content": "# 关于我\n\n“我是谁？”，是个不错的哲学问题。\n\n在江湖上，大家亲切地称我 “小马哥“，我做公益，也做生意；在社区中，我又以 `mercyblitz` 的身份出没在众多开源项目，\"mercy\" 符合我的性格，\"blitz\" 说明我的风格。\n\n承蒙错爱，不少的朋友对我过去的分享称赞有加，然而“千人之诺诺，不如一士之谔谔”，时常又让自己陷入一种迷思，到底是平台的帮衬，还是个人的确禁得起考验。于是我选择隐匿真名，希望能够听到更为真实的声音。尽管在互联网时代，只要稍作功课，个人信息几乎无处遁形。无可讳言，本人的所属公司以及职业头衔必然会形成“舞台效应”，如此一来，不但违背了写书的初衷，而且模糊了讨论的焦点。所以，本书即不会出现这些信息，又不会搞“个人崇拜”。它的价值应该体现在知识的传播，至于它的优劣则由诸君来评判。\n\n\n\n\n## 个人简介\n\n[Java 劝退师](https://www.douyu.com/mercyblitz)，[Apache Dubbo](https://dubbo.apache.org/) PMC、[Spring Cloud Alibaba](https://github.com/spring-cloud-incubator/spring-cloud-alibaba) 项目架构师。目前主要负责集团中间件开源项目、微服务技术实施、架构衍进、基础设施构建等。通过 SUN Java（SCJP、SCWCD、SCBCD）以及 Oracle OCA 等的认证。\n\n\n\n\n## 交流社区\n\n- 微信公众号：次灵均阁\n  \n![微信公众号二维码](assets/my_mp_qrcode.jpg)\n\n- [知识星球](https://t.zsxq.com/72rj2rr)：\n  \n![小马哥 Java 星球](assets/my_java_planet.png)\n\n- [Github](http://github.com/mercyblitz)：[http://github.com/mercyblitz](http://github.com/mercyblitz)\n\n> 更多个人信息，请使用 [Google 搜索 `mercyblitz`](https://www.google.com/search?q=mercyblitz)\n\n\n\n\n## 主要开源项目\n\n- [Spring Cloud Alibaba](https://github.com/spring-cloud-incubator/spring-cloud-alibaba)：https://github.com/spring-cloud-incubator/spring-cloud-alibaba\n- [Apache Dubbo](https://github.com/apache/incubator-dubbo)：https://github.com/apache/incubator-dubbo\n- [Nacos](https://github.com/alibaba/nacos)：https://github.com/alibaba/nacos\n"
  },
  {
    "path": "books/thinking-in-spring-boot/conventions.md",
    "content": "# 《Spring Boot 编程思想》 - 相关约定\n\n\n本书在议题的讨论中，将在文档引用、示例代码、日志输出、源码版本、源码省略等方面做出约定。\n\n\n\n\n## 文档引用约定\n\n在文档引用方面，Spring Boot 官方文档的默认版本为 `2.0.2.RELEASE`，即网址：\n[https://docs.spring.io/spring-boot/docs/2.0.2.RELEASE/reference/htmlsingle/](https://docs.spring.io/spring-boot/docs/2.0.2.RELEASE/reference/htmlsingle/)\n\nSpring Framework 官方文档则选择 `5.0.6.RELEASE`，地址为：[https://docs.spring.io/spring/docs/5.0.6.RELEASE/spring-framework-reference/](https://docs.spring.io/spring/docs/5.0.6.RELEASE/spring-framework-reference/)\n\n为了遵照原文，讨论的过程中将援引原文，如 Spring Boot 官方文档在 [\"11.5 Creating an Executable Jar\"](https://docs.spring.io/spring-boot/docs/2.0.2.RELEASE/reference/htmlsingle/#getting-started-first-application-executable-jar) 章节中介绍此等构建方式，即构建可执行 JAR，又称之为 \"fat jars\"\n\n> We finish our example by creating a completely self-contained executable jar file that we could run in production. Executable jars (sometimes called “fat jars”) are archives containing your compiled classes along with all of the jar dependencies that your code needs to run.\n\n此处的 Spring Boot 官方文档版本为 `2.0.2.RELEASE`，否则在讨论的内容中会特别说明其他版本信息。\n\n\n\n\n## 示例代码约定\n\n为了减少代码冗余和内容篇幅，因此 Java 示例代码通常会省略其 `package` 和 `import` 部分的代码，如下所示：\n\n```java\n@SpringBootApplication\npublic class FirstAppByGuiApplication {\n\n    public static void main(String[] args) {\n        SpringApplication.run(FirstAppByGuiApplication.class, args);\n    }\n\n    @Bean\n    public RouterFunction<ServerResponse> helloWorld() {\n        return route(GET(\"/hello-world\"),\n                request -> ok().body(Mono.just(\"Hello,World\"), String.class)\n        );\n    }\n}\n```\n\n如果该示例被重构或调整多次，其不变的代码将会被省略，仅关注变更或核心代码：\n\n```java\n@SpringBootApplication\npublic class FirstAppByGuiApplication {\n    ...\n\n    /**\n     * {@link ApplicationRunner#run(ApplicationArguments)} 方法在\n     * Spring Boot 应用启动后回调\n     *\n     * @param context WebServerApplicationContext\n     * @return ApplicationRunner Bean\n     */\n    @Bean\n    public ApplicationRunner runner(WebServerApplicationContext context) {\n        return args -> {\n            System.out.println(\"当前 WebServer 实现类为：\"\n                    + context.getWebServer().getClass().getName());\n        };\n    }\n}\n```\n\n不难发现，为了方便理解，在示例代码中会添加必要的注释加以说明。同时，如果在调整的过程中，若出现颠覆性变化的话，通常将注释无需的功能，方便后续回顾：\n\n```java\n//@Configuration\n//@ComponentScan\n@EnableAutoConfiguration\n//@SpringBootApplication(scanBasePackages = \"thinking.in.spring.boot.config\")\npublic class FirstAppByGuiApplication {\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(FirstAppByGuiApplication.class, args);\n\t}\n\n//    /**\n//     * {@link ApplicationRunner#run(ApplicationArguments)} 方法在\n//     * Spring Boot 应用启动后回调\n//     *\n//     * @param context WebServerApplicationContext\n//     * @return ApplicationRunner Bean\n//     */\n//    @Bean\n//    public ApplicationRunner runner(WebServerApplicationContext context) {\n//        return args -> {\n//            System.out.println(\"当前 WebServer 实现类为：\"\n//                    + context.getWebServer().getClass().getName());\n//        };\n//    }\n}\n```\n\n同样的原则适用于 XML 文件或其他配置文件：\n\n```xml\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>thinking-in-spring-boot</groupId>\n    <artifactId>first-app-by-gui</artifactId>\n    <version>0.0.1-SNAPSHOT</version>\n    <packaging>war</packaging>\n    ...\n        <!--&lt;!&ndash; Use Jetty instead &ndash;&gt;-->\n        <!--<dependency>-->\n            <!--<groupId>org.springframework.boot</groupId>-->\n            <!--<artifactId>spring-boot-starter-jetty</artifactId>-->\n        <!--</dependency>-->\n\n        <!--&lt;!&ndash; Use Undertow instead &ndash;&gt;-->\n        <!--<dependency>-->\n            <!--<groupId>org.springframework.boot</groupId>-->\n            <!--<artifactId>spring-boot-starter-undertow</artifactId>-->\n        <!--</dependency>-->\n\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-tomcat</artifactId>\n        </dependency>\n    ...\n</project>\n```\n\n\n\n## 日志输出约定\n\n为了精简示例运行时的日志输出，书中的内容并无完全与实际情况相同，主要的差异在于其中移除了重复以及相关时间等非重要信息，如\n\n```\n$ mvn spring-boot:run\n(...部分内容被省略...)\n[           main] o.s.w.r.f.s.s.RouterFunctionMapping      : Mapped (GET && /hello-world) -> thinkinginspringboot.firstappbygui.FirstAppByGuiApplication$$Lambda$276/708609190@7af17431\n(...部分内容被省略...)\n当前 WebServer 实现类为：org.springframework.boot.web.embedded.tomcat.TomcatWebServer\n[           main] t.f.FirstAppByGuiApplication             : Started FirstAppByGuiApplication in 2.119 seconds (JVM running for 5.071)\n```\n\n当内容中出现 \"(...部分内容被省略...)\" 时，说明其中省略数行的日志内容，并且几乎所有的日志输出到标准输出（`System.out`）。\n\n\n\n\n## 源码版本约定\n\n在源码分析过程中，为了厘清不同版本中的实现细节和变化，通常在目标代码下方带有 Spring Boot 版本信息，以及 Maven 依赖的 GAV 坐标信息（GAV = `groupId`、`artifactId` 以及 `version`)，如下所示：\n\n```java\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@Inherited\n@Configuration\n@EnableAutoConfiguration\n@ComponentScan\npublic @interface SpringBootApplication {\n\n\t/**\n\t * Exclude specific auto-configuration classes such that they will never be applied.\n\t * @return the classes to exclude\n\t */\n\tClass<?>[] exclude() default {};\n\n}\n```\n> 以上实现源码源于 Spring Boot `1.2.8.RELEASE`\n> > Maven GAV 坐标为： `org.springframework.boot:spring-boot-autoconfigure:1.2.8.RELEASE`\n\n如果以上版本信息没有出现时，Spring Boot 默认采用 `2.0.2.RELEASE`，Spring Framework 则选择 `5.0.6.RELEASE`，JDK 源码的版本则是 `1.8.0_172`。\n\n\n\n\n## 源码省略约定\n\n在源码分析的过程中，考虑到实现代码可能相对繁杂，为观其大意，便于记忆，期间将会注释或移除部分无关痛痒的内容，如：\n\n```java\npublic class AutoConfigurationImportSelector\n\t\timplements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,\n\t\tBeanFactoryAware, EnvironmentAware, Ordered {\n    ...\n    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,\n\t\t\tAnnotationAttributes attributes) {\n\t\tList<String> configurations = SpringFactoriesLoader.loadFactoryNames(\n\t\t\t\tgetSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());\n\t\t...\n\t\treturn configurations;\n\t}\n    ...\n\tprotected Class<?> getSpringFactoriesLoaderFactoryClass() {\n\t\treturn EnableAutoConfiguration.class;\n\t}\n    ...\n}\n```\n\n当然，其省略的部分并未一无是处，而是根据小马哥个人经验来筛选，必然存在个人主观的影响，建议读者先结合对应的版本源码，整体把握，逐步形成选读的意识。\n\n\n\n\n## 表达约定\n\n本书的讨论内容可能对相同事务出现不同的表述方式，如：\n\n- 注解：Annotation\n\n- 配置 Class：`@Configuration` 类、`@Configuration` Class、 Configuration Class\n\n- 包：`package`\n\n- 类路径：Class Path、class-path、类路径\n\n\n\n\n## [返回](/books/thinking-in-spring-boot/)"
  },
  {
    "path": "books/thinking-in-spring-boot/core/preface.md",
    "content": "# 自序\n\n非常感谢您阅读本书，在成长道路上，我们从此不再孤单。\n\n大约在三年前，鄙人有幸参与全集团微服务架构的衍进以及基础设施的构建，期间痛苦和受益并存。二零一六年十二月，经朋友引荐，作为 “SFDC 2016 杭州开发者大会” 的嘉宾，分享了一场名为《微服务实践之路》的演讲，从此正式开始了我的微服务布道师之路。次年三月，[segmentfault](https://segmentfault.com) “讲堂”栏目上线，本人再次受邀，作为 Java 讲师，于同年六月二日[《Java 微服务实践》](https://segmentfault.com/n/1330000009887617)系列讲座正式直播，主讲 Spring Boot 和 Spring Cloud。无独有偶，当月正好本人工作满十周年，也萌生了著书的意向，计划写一本关于 Spring Boot 微服务开发实践的书籍，希望借此机会与诸君分享我的微服务实践经验，然而随后的变故将此念头变为了现实。当月九号上午，正值当差，父亲传来一通电话，告知外婆于八点左右过世，听此噩耗，悲从中来，不可断绝。即刻带着身怀六甲的妻子，启程回湘。\n\n外婆一直陪伴着我的成长，直到我远赴杭州求职，才分隔两地。现如今祖孙二人天各一方，生死茫茫，无处话凄凉，子欲养而亲不待的痛楚莫过于此。我曾向上天祷告，愿她能安享西方极乐。若非外婆的离世，我绝对不会有坚定意志和足够勇气来完成此书，书籍的内容也不会有颠覆性的变化，讨论的议题从过去的”Spring Boot 微服务开发实践“逐渐转变为 ”Spring Boot 编程思想“。希望竭尽所能，将技术积累、学习方法、实战经验，以及所思所想和盘托出。每当自己午夜梦回，脑海中浮现外婆的容貌时，总会潸然泪下，所有的思想动摇和行为慵懒立即烟消云散。外婆是虔诚的佛教徒，平日乐善好施，从小耳濡目染的我也尽一点绵薄之力，将书籍五成的稿费将作为公益基金，支持贫困地区的小朋友教育，并且不定期地公开账目信息，供广大读者朋友监督。这或许有些杯水车薪，希望他们能够感到一丝的温暖。\n\n祸兮福所倚，福兮祸所伏，生死轮回，自然之理。外婆去世后的两个月，我的儿子降临人间。作为一名新晋的父亲，自然会以更高的标准来要求自我，对书籍的质量同样趋于严苛，将早期已完成的部分”付之一炬“，推倒重来，内容篇幅剧增。作为我儿的表率，著书只是”立言“的开始，捐赠作为”立德“的发端，而”立行“则需身体力行，持之以恒。或许”著作等身“是一种不错的选择，然而现代科技的进步，尤其是文字载体的革新，要做着这点，实在难度不小。不过，“为者常成，行者常至”，实现从”小马哥“到”马三立“先生的华丽转身并非遥不可及。\n\n除个人情感因素之外，今年四月，中兴通讯（以下简称为”中兴“）被美国制裁事件爆发后，再次刺激了我的历史情愫，希望写作手法的转变能够表达一些治学的态度。\n\n”中兴“二字是多么具有历史渊源的文字。曾几何时，多少英雄豪杰为国为民，挽狂澜于既倒，扶大厦之将倾。中国历史著名的“少康中兴“、”光武中兴“ 等，其成功的原因不外乎任贤使能、赏罚分明。然而回到现代，”中兴“却被老百姓当做茶余饭后调侃和讽刺的对象，多少令人惋惜。抛开政治因素，不少媒体认为中兴没有掌控芯片制造技术，也不具备高精尖技术的研发能力。有甚者认为即使是国内的 BAT 也并非尖端科技企业，而是商业模式的创新。这看似以偏概全的言论，不是毫无道理。还有人指出，自 1840 年中英鸦片战争以来，帝国主义灭我中华之心不减，过去利用其船坚炮利打开中国之门，如今运用高科技手段欺压民族复兴。媒体总习惯用耸动的标题以及挑逗的文字，刺激和取悦读者。然而在舆论风波过后，仍旧马照跑，舞照跳，尤其在互联网信息时代，人们的记忆是短暂的，目光也是短浅的。不过这世间不乏提出问题的旁观者，却缺少“行有不得，反求诸己“的实践者。\n\n假设中国人在智力上无法问鼎科技的顶峰，那又如何解释杨振宁、李政道以及李远哲等这一批华人诺贝尔得主呢？虽然他们生长在中国，然而功成在美国，又是什么原因让他们成为了“橘生淮南则为橘，生于淮北则为枳”的典型代表呢？不少专家学者认为由于新中国的教育或者教育制度存在不足，延缓了科技进步的步伐。如果将所有问题归咎于现代教育，恐怕是有失公允的。引用大学恩师陈不功先生的青年时期留学的经历，据他观察，在犹太学生的课堂，近三分之二的时间是老师解答学生的发问，剩余三分之一的时间才给老师传授新知识。而美国学生次之，各占一一半的时间。相反，中国学生几乎不提出问题，均等老师讲授。按照他的说法，这就是民族思想的差异，中国学生不善于独立思考，而习惯于被动接受，然而长期定居在美国的华人则接近于美国本土学生，成长的氛围和周围的环境至关重要。除此之外，个人认为缺少辩证思维同样导致科技窒碍难行的重要因素，这些均源于历史的沉疴。\n\n春秋战国时期，尽管礼崩乐坏、兄弟阋墙，然而正值诸侯伐交，百家争鸣，形成了文化多元的社会，成就了中华文明最璀璨的时代。自秦始皇扫六合，框天下，形成了车同轨，书同文的大一统帝国，华夏民族性格从此留下了统一的思想烙印。至汉武帝时期，朝野推行“罢黜百家，独尊儒术”的国策。不可否认，儒学思想作为华夏文明的瑰宝，在“修治平”方面的影响是巨大的，然而也存在一些“夷狄之有君，不如诸夏之亡也”，“微管仲,吾披发左衽已”等“尊华贬夷”的思想。同时，“君君，臣臣，父父，子子”又体现其思想保守的一面。当儒家思想成为绝对的主流，其他学说被边缘化之后，中华文化势必形成单一化的趋势，一旦对其批判，则立即视作离经叛道，异端邪说，为天下笑。尽管隋唐出现的科举制度能够实现“朝为田舍郎，暮登天子堂”的命运转变，反而让百姓普遍认为”万般皆下品，惟有读书高“。明代的八股取仕更是将思想锁定在四书五经，“学而优则仕”是读书的终极目地，死记硬背则是学习的不二法门，无形之中将单一化思维推向了巅峰。由于常年累月所形成的思维单一、固步自封，盲目自大等民族特质，即使遇到“西学东渐”的历史机遇，也无法避免民族的蒙羞，只会出现“中学为体，西学为用”的思潮。实际上，中华文化在哲学上有着无出其右的丰硕成果，“格物致知”是科学探索的精神基础，“中庸之道“是理性和感性的平衡，不过后世多半争做“坐而论道”的士大夫，却不齿“作而行之”的巫医乐师百工之人。是故，思想决定高度，解放思想，方得要领，否则，终究是桥归桥、路归路。\n\n已故南京大学历史系教授高华先生曾引述凯斯·詹京斯的观点，“历史乃论述过去，但绝不等于过去”。既然是论述或多或少会存在着偏差，不但受限于论述者的知识、能力以及记忆等主观因素，而且取决于当时的时空环境。为了遵照原著，在功能特性的介绍上，本书将引述官方文档的英文原文，并做出适当的解释。由于文档的编写者或许不是代码的实现者，即使是实现者本人，难免不会站在自己的立场和高度，抑或章节安排以及文字组织等诸多因素影响阅读和理解。因此，针对官方文档语焉不详的部分，本书将会补充说明；对其错误的结论，将会加以修正。由于本人能力和水平的局限，不敢妄言理解“格物致知”的奥义，难免有主观臆断和勘误谬论之处，且仅一家之言，供诸君参考，切莫将此奉为圭臬，书云亦云，不假思索。老子有言：”上士闻道，勤而行之“，希望读者能学以致用，若能在实践中激发出创新的灵感，善莫大焉。\n\n最后，借此机会，由衷地感谢我的太太，没有她背后默默地付出，我不会有如此多精力的投入，更无法专注写作。同时，向陈编辑晓猛先生致敬，他是一位谦谦君子，极富耐心，在书籍编写的过程中，给予了我不少的帮助和鼓励。还有再次向各位朋友送上我诚挚的歉意，由于个人的原因，使得书籍出版时间一再跳票。\n\n\n<div style=\"text-align: right\">小马哥</div>\n<div style=\"text-align: right\">公元二零一八年 十一月 于杭州</div>\n"
  },
  {
    "path": "books/thinking-in-spring-boot/donate.md",
    "content": "> 更新时间：2019-11-12\n\n## 稿费明细\n\n| 打款日期   | **结算册数** | 税前稿费 | 税后稿费 | 税金    |\n| ---------- | ------------ | -------- | -------- | ------- |\n| 2019-06-18 | 2500         | 23600.00 | 20956.80 | 2643.20 |\n| 2019-09-17 | 2500         | 23600.00 | 20956.80 | 2643.20 |\n| 2019-09-17 | 3000         | 28320.00 | 25148.16 | 3171.84 |\n| 2019-09-17 | 2000         | 18880.00 | 16765.44 | 2114.56 |\n| 2019-11-20 | 5000         | 53100.00 | 47152.80 | 5947.20 |\n| 2019-12-06 | 5000         | 59000.00 | 52392.00 | 6608.00 |\n|            |              |          |          |         |\n| 总计       | 20000        | 206500   | 183372   | 23128   |\n\n> PS：**书籍实际出版册数会高于已结稿册数**\n\n\n\n## 公益资金明细\n\n公益应支出： 183372 * 50% = 91686 元\n\n实际支付：0 元\n\n> 欢迎大家推荐，仅用于贫困地区的小朋友教育，联系人微信：mercyblitz-1985\n\n\n\n## [返回](/books/thinking-in-spring-boot/)\n"
  },
  {
    "path": "books/thinking-in-spring-boot/overview.md",
    "content": "# 《Spring Boot 编程思想》 - 内容总览\n\n由于本书所讨论的内容跨度广泛，功能特性鲜明，由《核心篇》、《运维篇》以及《Web 篇》三册分别讨论之。《核心篇》开篇总览 Spring Boot 核心特性，逐一讨论 [Spring Boot 官网](https://spring.io/projects/spring-boot) 所罗列之六大特性，然而其中两点并非 Spring Boot 专属，故点到为止，而将其他特性归纳为五大议题，分别为 “自动装配（Auto-Configuration）”、`SpringApplication`、“外部化配置”、“Spring Boot Actuator” 以及 “嵌入式 Web 容器”。其中，前两者是《核心篇》讨论的议题，后两者则是 Spring Boot 官方定义的 \"Production-Ready\" 特性，均偏向 Spring Boot 应用运维，因此纳入《运维篇》的讨论范畴。至于“嵌入式 Web 容器”，它将结合传统 Java EE Servlet、Spring Web MVC 以及 Spring 5 WebFlux 内容放至《Web 篇》探讨，具体议题安排如下：\n\n- 《核心卷》\n    - 总览 Spring Boot\n    - 走向自动装配\n    - 理解 `SpringApplication`\n\n- 《运维卷》\n    - 超越外部化配置\n    - 简化 Spring 应用运维体系\n\n- 《Web 卷》\n    - 渐行渐远的 Servlet\n    - 从 Servlet 到 Web MVC\n    - 从 Reactive 到 WebFlux\n    - 嵌入式 Web 容器\n\n在《核心卷》中，“走向自动装配”是以 Spring Boot 自动装配特性为核心议题，从 Spring Framework 时代展开讨论。从 Spring Framework 1.x 伊始，Spring 注解驱动编程模型逐渐发展成熟，包括 Spring ”元注解(Meta-Annotations) “、”模式注解(Stereotype Annotations) “，”组合注解（Composed Annotations）“以及”注解属性别名和覆盖（Attribute Aliases and Overrides）“。与此同时，Spring 注解驱动也形成了自成一派的设计模式，可总结为 “`@Enable`  模块驱动“，”Web 自动装配“和”条件装配“。拥有强大的 Spring 注解驱动的加持，Spring Boot 自动装配的出现不过是顺水推舟的事情。因此，它的形成过程自然是讨论的焦点。或许，自动装配是 Spring Boot 最新显著的特性，然而这并非核心特性的全部。事实上，Spring Framework 在 Spring 应用上下文生命周期的设计上存在一些的”瑕疵“，如 Spring 事件/监听时机较晚、`PropertySource` 扩展不便等。Spring Boot 作为Spring Framework 延伸，引入了全新 `SpringAppplication` API。从此，Spring Boot 具备了管理 Spring 应用上下文生命周期的能力，进而衍生出 Spring Boot 事件（Spring 事件扩展）以及外部化配置（`PropertySource` 扩展）等全新特性。所以，”理解 `SpringAppplication`“ 部分的技术细节成为了《核心卷》结尾深入探讨的议题。\n\n目前，《核心卷》和《运维卷》已编写完毕，《Web 卷》正在同步更新中，内容安排可能发生变更，请读者以最终发行为准。\n\n在内容结构上，本书采用“总分总”的方式，首先总体介绍讨论范围，随后深入展开细节的讨论，最后予以总结。同时，为了避免先入为主的影响，本书将会针对官方文档的描述内容提出疑问或假设，大胆地猜测其可能实现的方式，再结合实现源码加以验证，随后将通过示例代码巩固理解。在写作手法上，本书效仿传统中国历史书籍的编著手法，将纪传体和编年体予以综合。如果从功能特性来看，它属于纪传体，如自动装配、`SpringApplication`，以及外部化配置等。如此表述的方式更容易系统地掌握 Spring Boot 以及 Spring Framework 的核心特性。如果从特性的发展历程来观察，它则属于编年体，如 Spring Framework 注解驱动编程模型从 1.x 到 5.0 中的发展与 Spring Boot 自动装配之间的关联，以及 Spring Boot 1.0 到 1.4 的外部化配置源是怎样利用 Spring Environment 抽象逐步完善等。更为重要的是，在论述方式上，增加了论点、论证以及论据，从而知其然知其所以然。在特性的讨论过程中，补充说明的内容可能会穿插其中（“小马哥提示您”将在出版物中删除）。在特性讨论的结尾处，“小马哥有话说”将总结所论议题，并且发表感想，类似于《史记》中的“太史公曰”。\n\n所谓 ”兼听则明，偏听则暗”，本书的讨论范围并不会局限在 Spring Boot 或 Spring Framework，也将 Spring Cloud 甚至是 Spring Cloud Data Flow 纳入参考，探讨 Spring Boot 在两者中的运用。站在更为宏观的角度，在整个 Java EE 的生态中，Spring 技术栈并非独此一家，也不完全是“开山之作”，不少相关的特性可在 JSR 规范以及其他 Java EE 实现中找到原型。换言之，Spring 技术栈可认为是一种非常成功的“重复发明轮子”，不仅适配了 JSR 实现，而且“借鉴”了他山之石，逐步实现了自身的生态系统。\n\n总而言之，全书的讨论将以 Spring Boot 为中心，议题发散至 Spring 技术栈、JSR 以及 Java。希望透过全局的视角，了解变迁的历程；经过多方的比较，理解特性的原理；综合标准的规范，掌握设计的哲学。当您纵览全书之后，或许将会明白为什么说 “Spring Boot 易学难精“，因为它的核心是 Spring Framework，而后者的理解程度又取决于 JSR 规范以及 Java 的熟悉度。或许 Spring 技术栈不算一流的技术，却是一等的成功。\n\n\n\n## [返回](/books/thinking-in-spring-boot/)"
  },
  {
    "path": "books/thinking-in-spring-boot/revision.md",
    "content": "# 《Spring Boot 编程思想》 - 勘误汇总\n\n如果您在阅读《Spring Boot 编程思想 - 核心篇》或示例练习的过程中发现了其中错误，请将错误内容提交至[【勘误汇】](https://github.com/mercyblitz/thinking-in-spring-boot-samples/issues)，小马哥将勘误内容汇总到此，修正后的内容将在后续的书籍发行中体现，并将勘误贡献者 Github ID 刊登到修订版的书籍中。\n\n> 小马哥水平有限，行文的过程中错误无法避免，为此深表歉意。\n\n\n\n## 核心篇\n\n### 基本信息\n\n书名：《Spring Boot 编程思想 - 核心篇》\n\nISBN：978-7-121-36039-8\n\n### [版次：2019 年 3 月第 1 版](https://www.yuque.com/docs/share/0998f64a-9538-4b60-9647-bb9f65c43530#e63c23f7)\n\n- [勘误详情](https://www.yuque.com/docs/share/0998f64a-9538-4b60-9647-bb9f65c43530#e63c23f7)\n- [问题反馈](https://github.com/mercyblitz/thinking-in-spring-boot-samples/issues/3)\n\n\n\n\n## [返回](/books/thinking-in-spring-boot/)\n\n"
  },
  {
    "path": "books/thinking-in-spring-boot/samples.md",
    "content": "# 《Spring Boot 编程思想》- 示例工程\n\n《Spring Boot 编程思想》所有的示例代码均存放在 GitHub 工程 [https://github.com/mercyblitz/thinking-in-spring-boot-samples](https://github.com/mercyblitz/thinking-in-spring-boot-samples)，该工程为标准的 Maven 多模块工程，运行时要求为 [Java](https://www.oracle.com/technetwork/java/index.html) `1.8+` 以及 [Maven](https://maven.apache.org/) `3.2.5+`。其协议为 \"[Apache License Version 2.0](https://github.com/mercyblitz/thinking-in-spring-boot-samples/blob/master/LICENSE)\"，不必担心商业用途所带来的风险。由于本书尚未完全截稿，工程结构未来可能存在微调。因此，当前内容无法确保百分之百匹配，请读者定期关注 [`README.md`](https://github.com/mercyblitz/thinking-in-spring-boot-samples) 文件，确保咨询的更新。\n\n\n\n\n## 工程结构\n\n示例工程 `thinking-in-spring-boot-samples` 的结构如下图所示：\n\n![工程结构](assets/project_structure.png)\n\n该工程包含五个子模块以及四个文件，分别是：\n\n- 子模块\n  \n| 子模块                                                       | 说明                                                         |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n| [shared-libraries](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/shared-libraries) | 共享类库，为其他工程提供基础 API 或依赖                      |\n| [spring-boot-1.x-samples](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-boot-1.x-samples) | Spring Boot 1.x 示例工程，包含六个子模块，主要用于参考和对比 Spring Boot 1.x 各版本中的实现差异，并且提供章节示例代码实现 |\n| [spring-boot-2.0-samples](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-boot-2.0-samples) | Spring Boot 2.0 示例工程，也是主示例工程，以 `2.0.2.RELEASE` 作为基础版本 |\n| [spring-framework-samples](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-framework-samples) | Spring Framework 示例工程，作为 Spring Boot 底层实现框架，版本范围从 2.0 到 5.0 |\n| [traditional-samples](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/traditional-samples) | 传统 Java EE 示例工程，用于理解 Java EE 与 Spring Boot 关联和差异 |\n\n- 文件\n\n| 文件                                                         | 说明                          |\n| ------------------------------------------------------------ | ----------------------------- |\n| [.gitignore](https://github.com/mercyblitz/thinking-in-spring-boot-samples/blob/master/.gitignore) | Git 版本控制忽略文件          |\n| [LICENSE](https://github.com/mercyblitz/thinking-in-spring-boot-samples/blob/master/LICENSE) | 工程许可文件                  |\n| [README.md](https://github.com/mercyblitz/thinking-in-spring-boot-samples/blob/master/README.md) | 工程说明文件                  |\n| [pom.xml](https://github.com/mercyblitz/thinking-in-spring-boot-samples/blob/master/pom.xml) | 示例工程 Maven `pom.xml` 文件 |\n\n其中，又以 [spring-boot-2.0-samples](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-boot-2.0-samples) 、[spring-boot-1.x-samples](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-boot-1.x-samples) 以及 [spring-framework-samples](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-framework-samples) 为本示例工程最核心的子模块，对此将详细说明。\n\n\n\n\n## 子模块 [spring-boot-2.0-samples](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-boot-2.0-samples)\n\n[spring-boot-2.0-samples](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-boot-2.0-samples) 作为《Spring Boot 编程思想》的主示例工程，基于 Spring Boot `2.0.2.RELEASE` 实现，由若干个子模块组成，这些模块与章节所讨论的议题紧密关联：\n\n```\n├── spring-boot-2.0-samples\n│   ├── auto-configuration-sample\n│   ├── externalized-configuration-sample\n│   ├── first-app-by-gui\n│   ├── first-spring-boot-application\n│   ├── formatter-spring-boot-starter\n│   ├── pom.xml\n│   ├── production-ready-sample\n│   ├── spring-application-sample\n│   ├── spring-boot-2.0-samples.iml\n│   └── traditional-web-sample\n```\n\n按照本书章节的安排，模块与章节所对应的关系依次为：\n\n| 子模块                                                       | 说明                                                         | 篇章                              |\n| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------- |\n| [first-spring-boot-application](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-boot-2.0-samples/first-spring-boot-application) | 基于 Maven 插件构建的第一个 Spring Boot 应用                 | 《核心篇 - 总览Spring Boot》          |\n| [first-app-by-gui](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-boot-2.0-samples/first-app-by-gui) | 基于图形化界面 https://start.spring.io/ 构建的第一个 Spring Boot 应用 | 《核心篇 - 总览Spring Boot》          |\n| [auto-configuration-sample](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-boot-2.0-samples/auto-configuration-sample) | Spring Boot 自动装配示例                                     | 《核心篇 - 走向自动装配》             |\n| [formatter-spring-boot-starter](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-boot-2.0-samples/formatter-spring-boot-starter) | Spring Boot 自动装配 Starter 示例                            | 《核心篇 - 走向自动装配》             |\n| [spring-application-sample](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-boot-2.0-samples/spring-application-sample) | Spring Boot `SpringApplication` 示例                          | 《核心篇 - 理解 `SpringApplication`》 |\n| [externalized-configuration-sample](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-boot-2.0-samples/externalized-configuration-sample) | Spring Boot 外部化配置示例                                   | 《运维篇 - 超越外部化配置》           |\n| [production-ready-sample](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-boot-2.0-samples/production-ready-sample) | Spring Boot Production-Ready                                 | 《运维篇 - 简化 Spring 应用运维体系》 |\n| [traditional-web-sample](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-boot-2.0-samples/traditional-web-sample) | Spring Boot 应用部署到传统 Servlet 容器示例                  | 《Web篇 - “渐行渐远“的 Servlet》      |\n\n除此之外，相关示例代码部分也可能放置在其他子模块，如 “走向自动装配” 章节中，大量的实例代码在子模块 [spring-framework-samples](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-framework-samples) 之中。\n\n\n\n\n## 子模块 [spring-boot-1.x-samples](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-boot-1.x-samples)\n\n前文提到，该[子模块](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-boot-1.x-samples) 包含六个子模块，它们对应了所有的 Spring Boot 1.x 实现版本：\n\n```\n├── spring-boot-1.x-samples\n│   ├── pom.xml\n│   ├── spring-boot-1.0.x-project\n│   ├── spring-boot-1.1.x-project\n│   ├── spring-boot-1.2.x-project\n│   ├── spring-boot-1.3.x-project\n│   ├── spring-boot-1.4.x-project\n│   ├── spring-boot-1.5.x-project\n```\n\n由于截止到当前编写时间，恰逢 Spring Boot 1.5 的发行版本为 `1.5.10.RELEASE`，而1.5 之前的版本则已停止维护，可选择其最后发行的版本作为参考，故子模块、Spring Boot 1.x 版本以及对应 Spring Framework 的关系，如下表格所示：\n\n| 子模块                                                       | Spring Boot 1.x 版本 | Spring Framework 版本 |\n| ------------------------------------------------------------ | -------------------- | --------------------- |\n| [spring-boot-1.0.x-project](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-boot-1.x-samples/spring-boot-1.0.x-project) | `1.0.2.RELEASE`      | `4.0.3.RELEASE`       |\n| [spring-boot-1.1.x-project](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-boot-1.x-samples/spring-boot-1.1.x-project) | `1.1.9.RELEASE`      | `4.0.8.RELEASE`       |\n| [spring-boot-1.2.x-project](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-boot-1.x-samples/spring-boot-1.2.x-project) | `1.2.8.RELEASE`      | `4.1.9.RELEASE`       |\n| [spring-boot-1.3.x-project](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-boot-1.x-samples/spring-boot-1.3.x-project) | `1.3.8.RELEASE`      | `4.2.8.RELEASE`       |\n| [spring-boot-1.4.x-project](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-boot-1.x-samples/spring-boot-1.4.x-project) | `1.4.7.RELEASE`      | `4.3.9.RELEASE`       |\n| [spring-boot-1.5.x-project](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-boot-1.x-samples/spring-boot-1.5.x-project) | `1.5.10.RELEASE`     | `4.3.14.RELEASE`      |\n\n值得注意的是，子模块 [spring-boot-1.x-samples](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-boot-1.x-samples) 并非主示例工程，各章节讨论的特性示例并非面面俱到。相反，读者应重点关注子模块 [spring-boot-2.0-samples](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-boot-2.0-samples)。\n\n\n\n\n## 子模块 [spring-framework-samples](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-framework-samples)\n\n[spring-framework-samples](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-framework-samples) 作为例实现的辅助分析子模块，其版本涵盖 2.0 到 5.0，由于 [spring-boot-1.x-samples](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-boot-1.x-samples) 间接引入了 Spring Framework 4.0 到 4.3 的依赖，因此当前模块并未将 4.x 版本细分：\n\n```\n├── spring-framework-samples\n│   ├── pom.xml\n│   ├── spring-framework-2.0.x-sample\n│   ├── spring-framework-2.5.6-sample\n│   ├── spring-framework-3.0.x-sample\n│   ├── spring-framework-3.1.x-sample\n│   ├── spring-framework-3.2.x-sample\n│   ├── spring-framework-4.3.x-sample\n│   ├── spring-framework-5.0.x-sample\n│   └── spring-webmvc-3.2.x-sample\n```\n\n原则上，以上模块所选择 Spring Framework 版本与 [spring-boot-1.x-samples](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-boot-1.x-samples) 类似，故子模块与 Spring Framework 依赖关系如下所示：\n\n| 子模块                                                       | Spring Framework 版本 |\n| ------------------------------------------------------------ | --------------------- |\n| [spring-framework-2.0.x-sample](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-framework-samples/spring-framework-2.0.x-sample) | `2.0.8`               |\n| [spring-framework-2.5.6-sample](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-framework-samples/spring-framework-2.5.6-sample) | `2.5.6.SEC03`         |\n| [spring-framework-3.0.x-sample](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-framework-samples/spring-framework-3.0.x-sample) | `3.0.0.RELEASE`       |\n| [spring-framework-3.1.x-sample](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-framework-samples/spring-framework-3.1.x-sample) | `3.1.4.RELEASE`       |\n| [spring-framework-3.2.x-sample](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-framework-samples/spring-framework-3.2.x-sample) | `3.2.18.RELEASE`      |\n| [spring-framework-4.3.x-sample](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-framework-samples/spring-framework-4.3.x-sample) | `4.3.17.RELEASE`      |\n| [spring-framework-5.0.x-sample](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-framework-samples/spring-framework-5.0.x-sample) | `5.0.6.RELEASE`       |\n| [spring-webmvc-3.2.x-sample](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-framework-samples/spring-webmvc-3.2.x-sample) | `3.2.18.RELEASE`      |\n\n当读者发现子模块工程为仅包含 `pom.xml` 文件时，说明它引入的目地在于 Spring Framework 源码分析，用于比对版本间 Spring 特性的变迁和实现的差异。了解了示例工程的结构后，下一节的内容将接入示例代码的说明。\n\n\n\n\n## 示例代码说明\n\n由于本书几乎覆盖所有的 Spring Framework 以及 Spring Boot 版本，通常在配合章节说明时，绝大多数情况，示例代码的结尾部分带有 ”源码位置“的信息，且相对于 [https://github.com/mercyblitz/thinking-in-spring-boot-samples](https://github.com/mercyblitz/thinking-in-spring-boot-samples) 工程路径，如：\n\n```java\npublic class GenericEventListenerBootstrap {\n\n    public static void main(String[] args) {\n        // 创建 注解驱动 Spring 应用上下文\n        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();\n        // 注册 UserEventListener，即实现 ApplicationListener ，也包含 @EventListener 方法\n        context.register(UserEventListener.class);\n        // 初始化上下文\n        context.refresh();\n        // 构造泛型事件\n        GenericEvent<User> event = new GenericEvent(new User(\"小马哥\"));\n        // 发送泛型事件\n        context.publishEvent(event);\n        // 发送 User 对象作为事件源\n        context.publishEvent(new User(\"mercyblitz\"));\n        // 关闭上下文\n        context.close();\n    }\n\t...\n}\n```\n\n> 源码位置：以上示例代码，读者可查找 **spring-framework-samples/spring-framework-5.0.x-sample** 工程\n\n如果以上信息尚未提供，默认情况，如书中的 Spring Framework 示例存放在  **spring-framework-samples/spring-framework-5.0.x-sample** 工程，而 Spring Boot 示例存放在 **spring-boot-2.0-samples/** 所对应的章节工程。\n\n\n\n\n## UML 源文件说明\n\n书籍的讨论部分可能会引入相关的 UML 图，其源文件位于子项目工程的 `src/main/resources/uml` 目录中，文件扩展名为 `.ucls`。如子模块 [spring-application-sample](https://github.com/mercyblitz/thinking-in-spring-boot-samples/tree/master/spring-boot-2.0-samples/spring-application-sample)：\n\n```\nsrc/main/resources/uml/\n├── ApplicationContext.ucls\n├── ApplicationContextEvent.ucls\n├── ApplicationEventPublisher.ucls\n├── ConfigurableApplicationContext_ApplicationEventPublisher.ucls\n└── SimpleApplicationEventMulticaster.ucls\n```\n\n请读者使用 Eclipse 插件 \"ObjectAid UML Explorer\" 将其打开，插件下载地址：[http://www.objectaid.com/update/current](http://www.objectaid.com/update/current)，如文件 `ApplicationContext.ucls`：\n\n![ApplicationContext.png](assets/ApplicationContext_UML.png)"
  },
  {
    "path": "books/thinking-in-spring-boot/version.md",
    "content": "# 《Spring Boot 编程思想》 - 版本范围\n\n为了系统性地讨论 Spring Boot 的发展脉络，会将 Spring Boot 2.0 与 1.x 的版本加以对比，探索从 1.0 到 2.0 版本之间的重要变化，便于读者后续架构、整合以及迁移等工作，由于截止到当前编写时间，恰逢 Spring Boot `2.0.2.RELEASE` 版本发布，为统一源码分析，将讨论的 Spring Boot 2.0 版本固定在 `2.0.2.RELEASE`，相同时间点的 Spring Boot 1.5 的版本则是 `1.5.10.RELEASE`。同时由于更低的版本已停止维护，所以可选择其最后发行的小版本作为参考，故所有涉及的 Spring Boot 版本如下所示：\n\n- Spring Boot 2.0 : `2.0.2.RELEASE`\n- Spring Boot 1.5 : `1.5.10.RELEASE`\n- Spring Boot 1.4 : `1.4.7.RELEASE`\n- Spring Boot 1.3 : `1.3.8.RELEASE`\n- Spring Boot 1.2 : `1.2.8.RELEASE`\n- Spring Boot 1.1 : `1.1.9.RELEASE`\n- Spring Boot 1.0 : `1.0.2.RELEASE`\n\n由于 Spring Boot 2.0 最低依赖的 Java 版本为 8，而 Spring Boot 1.x 最低兼容 Java 1.6，因此 Java 1.8 是 Spring Boot 各个版本兼容的交集，也是后续的实例工程 `thinking-in-spring-boot-samples` 的运行时环境。除此之外，讨论将更多地关注 Spring Boot 与其核心依赖 Spring Framework 之间的版本映射关系。\n\n| Spring Boot 版本 | Spring Framework 版本 | JDK 版本 |\n| ---------------- | --------------------- | -------- |\n| `2.0.2.RELEASE`  | `5.0.6.RELEASE`       | 1.8+     |\n| `1.5.10.RELEASE` | `4.3.14.RELEASE`      | 1.6+     |\n| `1.4.7.RELEASE`  | `4.3.9.RELEASE`       | 1.6+     |\n| `1.3.8.RELEASE`  | `4.2.8.RELEASE`       | 1.6+     |\n| `1.2.8.RELEASE`  | `4.1.9.RELEASE`       | 1.6+     |\n| `1.1.9.RELEASE`  | `4.0.8.RELEASE`       | 1.6+     |\n| `1.0.2.RELEASE`  | `4.0.3.RELEASE`       | 1.6+     |\n\n不难看出，Spring Boot 2.0 对应的 Spring Framework 版本是 5.0，而 Spring Boot 1.x 则依赖于 Spring Framework 4.x。之所以要具体到 Spring Framework 的某个版本号，除了避免版本差异所导致源码分析失准的情况，更多的是由于 Spring 技术栈特殊的版本号管理。按照传统 Java 版本的约定，第一位数字表示主版本号，控制大版本更新，第二位则代表次版本号，可小范围地引入新的特性和相关 API，而第三位则用于问题修正或安全补丁等。而 Spring 技术栈不时地利用第三位版本号引入新的 API，典型的代表有 Spring Framework 3.0.1 引入的 API `BeanDefinitionRegistryPostProcessor`，Spring Boot 1.3.2 引入的 API `ExitCodeEvent`。同时，在框架 API 的兼容性，从 Spring Framework 到 Spring Cloud 逐渐降低，Spring Boot 处于比上不足比下有余的情况。因此，本书在深入讨论的过程中反复地强调 API 兼容的重要性，也希望读者在自研的过程中尤为关注。\n\n为了理解 Spring Boot 特性发展的过程，将 Spring Framework 版本讨论的范围从 1.x 到 5.0。换言之，本书将几乎涵盖所有的 Spring Framework 以及 Spring Boot 版本，包括两者所涉及的 [JSR](https://jcp.org/en/jsr/overview)（Java Specification Requests），如 Servlet 、Bean Validation 和 JAX-RS 等规范。\n\n> 小马哥提示您：更多的 JSR 资讯，请参考官方网页 [https://jcp.org/en/jsr/overview](https://jcp.org/en/jsr/overview)，或访问小马哥 JSR 收藏页面 [https://github.com/mercyblitz/jsr](https://github.com/mercyblitz/jsr)，下载归档的 JSR PDF 文件。\n\n\n\n\n## [返回](/books/thinking-in-spring-boot/)"
  },
  {
    "path": "books/thinking-in-spring-boot/videos.md",
    "content": "# 《Spring Boot 编程思想》 - 配套视频\n\n尽管本书巨细靡遗地讨论 Spring Boot 以及 Spring Framework 相关特性，不过它并非快速上手或使用教程，如果读者具备三年以上的开发经验或者资深的 Spring 用户，也许阅读起来会相对顺畅。反之，如果读者出现阅读缓慢或者困难的情况，不妨先参考官方文档，掌握基本使用技能，或者观看小马哥在慕课网的免费视频：\n\n- [Spring Boot 2.0深度实践-初遇Spring Boot](https://www.imooc.com/learn/933)：https://www.imooc.com/learn/933\n\n- [Spring Boot 2.0深度实践之系列总览](https://www.imooc.com/learn/1058)：https://www.imooc.com/learn/1058\n\n其中视频 “初遇Spring Boot” 先介绍 Spring Boot 2.0 基本特性，随后创建第一个 Spring Boot 应用，再将其改造成多 Maven 模块应用，这有助于对本书示例工程结构的理解。而 “系列总览” 则是《核心篇 - 总览 Spring Boot》章节对应的视频。换言之，小马哥慕课网的《Spring Boot 2.0深度实践》系列属于本书的配套视频。如果读者想要先睹为快的话，不妨访问网址 [https://www.imooc.com/t/5387391](https://www.imooc.com/t/5387391) 或扫描二维码参与报名：\n\n![我的慕课课程](assets/my_imooc_courses.png)\n\n\n\n\n## [返回](/books/thinking-in-spring-boot/)"
  },
  {
    "path": "css/bootstrap.css",
    "content": "/*!\n * Bootstrap v3.3.2 (http://getbootstrap.com)\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n\n/*! normalize.css v3.0.2 | MIT License | git.io/normalize */\nhtml {\n  font-family: sans-serif;\n  -webkit-text-size-adjust: 100%;\n      -ms-text-size-adjust: 100%;\n}\nbody {\n  margin: 0;\n}\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n  display: block;\n}\naudio,\ncanvas,\nprogress,\nvideo {\n  display: inline-block;\n  vertical-align: baseline;\n}\naudio:not([controls]) {\n  display: none;\n  height: 0;\n}\n[hidden],\ntemplate {\n  display: none;\n}\na {\n  background-color: transparent;\n}\na:active,\na:hover {\n  outline: 0;\n}\nabbr[title] {\n  border-bottom: 1px dotted;\n}\nb,\nstrong {\n  font-weight: bold;\n}\ndfn {\n  font-style: italic;\n}\nh1 {\n  margin: .67em 0;\n  font-size: 2em;\n}\nmark {\n  color: #000;\n  background: #ff0;\n}\nsmall {\n  font-size: 80%;\n}\nsub,\nsup {\n  position: relative;\n  font-size: 75%;\n  line-height: 0;\n  vertical-align: baseline;\n}\nsup {\n  top: -.5em;\n}\nsub {\n  bottom: -.25em;\n}\nimg {\n  border: 0;\n}\nsvg:not(:root) {\n  overflow: hidden;\n}\nfigure {\n  margin: 1em 40px;\n}\nhr {\n  height: 0;\n  -webkit-box-sizing: content-box;\n     -moz-box-sizing: content-box;\n          box-sizing: content-box;\n}\npre {\n  overflow: auto;\n}\ncode,\nkbd,\npre,\nsamp {\n  font-family: monospace, monospace;\n  font-size: 1em;\n}\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n  margin: 0;\n  font: inherit;\n  color: inherit;\n}\nbutton {\n  overflow: visible;\n}\nbutton,\nselect {\n  text-transform: none;\n}\nbutton,\nhtml input[type=\"button\"],\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n  -webkit-appearance: button;\n  cursor: pointer;\n}\nbutton[disabled],\nhtml input[disabled] {\n  cursor: default;\n}\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n  padding: 0;\n  border: 0;\n}\ninput {\n  line-height: normal;\n}\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n  -webkit-box-sizing: border-box;\n     -moz-box-sizing: border-box;\n          box-sizing: border-box;\n  padding: 0;\n}\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n  height: auto;\n}\ninput[type=\"search\"] {\n  -webkit-box-sizing: content-box;\n     -moz-box-sizing: content-box;\n          box-sizing: content-box;\n  -webkit-appearance: textfield;\n}\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n  -webkit-appearance: none;\n}\nfieldset {\n  padding: .35em .625em .75em;\n  margin: 0 2px;\n  border: 1px solid #c0c0c0;\n}\nlegend {\n  padding: 0;\n  border: 0;\n}\ntextarea {\n  overflow: auto;\n}\noptgroup {\n  font-weight: bold;\n}\ntable {\n  border-spacing: 0;\n  border-collapse: collapse;\n}\ntd,\nth {\n  padding: 0;\n}\n/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n@media print {\n  *,\n  *:before,\n  *:after {\n    color: #000 !important;\n    text-shadow: none !important;\n    background: transparent !important;\n    -webkit-box-shadow: none !important;\n            box-shadow: none !important;\n  }\n  a,\n  a:visited {\n    text-decoration: underline;\n  }\n  a[href]:after {\n    content: \" (\" attr(href) \")\";\n  }\n  abbr[title]:after {\n    content: \" (\" attr(title) \")\";\n  }\n  a[href^=\"#\"]:after,\n  a[href^=\"javascript:\"]:after {\n    content: \"\";\n  }\n  pre,\n  blockquote {\n    border: 1px solid #999;\n\n    page-break-inside: avoid;\n  }\n  thead {\n    display: table-header-group;\n  }\n  tr,\n  img {\n    page-break-inside: avoid;\n  }\n  img {\n    max-width: 100% !important;\n  }\n  p,\n  h2,\n  h3 {\n    orphans: 3;\n    widows: 3;\n  }\n  h2,\n  h3 {\n    page-break-after: avoid;\n  }\n  select {\n    background: #fff !important;\n  }\n  .navbar {\n    display: none;\n  }\n  .btn > .caret,\n  .dropup > .btn > .caret {\n    border-top-color: #000 !important;\n  }\n  .label {\n    border: 1px solid #000;\n  }\n  .table {\n    border-collapse: collapse !important;\n  }\n  .table td,\n  .table th {\n    background-color: #fff !important;\n  }\n  .table-bordered th,\n  .table-bordered td {\n    border: 1px solid #ddd !important;\n  }\n}\n@font-face {\n  font-family: 'Glyphicons Halflings';\n\n  src: url('../fonts/glyphicons-halflings-regular.eot');\n  src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');\n}\n.glyphicon {\n  position: relative;\n  top: 1px;\n  display: inline-block;\n  font-family: 'Glyphicons Halflings';\n  font-style: normal;\n  font-weight: normal;\n  line-height: 1;\n\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n.glyphicon-asterisk:before {\n  content: \"\\2a\";\n}\n.glyphicon-plus:before {\n  content: \"\\2b\";\n}\n.glyphicon-euro:before,\n.glyphicon-eur:before {\n  content: \"\\20ac\";\n}\n.glyphicon-minus:before {\n  content: \"\\2212\";\n}\n.glyphicon-cloud:before {\n  content: \"\\2601\";\n}\n.glyphicon-envelope:before {\n  content: \"\\2709\";\n}\n.glyphicon-pencil:before {\n  content: \"\\270f\";\n}\n.glyphicon-glass:before {\n  content: \"\\e001\";\n}\n.glyphicon-music:before {\n  content: \"\\e002\";\n}\n.glyphicon-search:before {\n  content: \"\\e003\";\n}\n.glyphicon-heart:before {\n  content: \"\\e005\";\n}\n.glyphicon-star:before {\n  content: \"\\e006\";\n}\n.glyphicon-star-empty:before {\n  content: \"\\e007\";\n}\n.glyphicon-user:before {\n  content: \"\\e008\";\n}\n.glyphicon-film:before {\n  content: \"\\e009\";\n}\n.glyphicon-th-large:before {\n  content: \"\\e010\";\n}\n.glyphicon-th:before {\n  content: \"\\e011\";\n}\n.glyphicon-th-list:before {\n  content: \"\\e012\";\n}\n.glyphicon-ok:before {\n  content: \"\\e013\";\n}\n.glyphicon-remove:before {\n  content: \"\\e014\";\n}\n.glyphicon-zoom-in:before {\n  content: \"\\e015\";\n}\n.glyphicon-zoom-out:before {\n  content: \"\\e016\";\n}\n.glyphicon-off:before {\n  content: \"\\e017\";\n}\n.glyphicon-signal:before {\n  content: \"\\e018\";\n}\n.glyphicon-cog:before {\n  content: \"\\e019\";\n}\n.glyphicon-trash:before {\n  content: \"\\e020\";\n}\n.glyphicon-home:before {\n  content: \"\\e021\";\n}\n.glyphicon-file:before {\n  content: \"\\e022\";\n}\n.glyphicon-time:before {\n  content: \"\\e023\";\n}\n.glyphicon-road:before {\n  content: \"\\e024\";\n}\n.glyphicon-download-alt:before {\n  content: \"\\e025\";\n}\n.glyphicon-download:before {\n  content: \"\\e026\";\n}\n.glyphicon-upload:before {\n  content: \"\\e027\";\n}\n.glyphicon-inbox:before {\n  content: \"\\e028\";\n}\n.glyphicon-play-circle:before {\n  content: \"\\e029\";\n}\n.glyphicon-repeat:before {\n  content: \"\\e030\";\n}\n.glyphicon-refresh:before {\n  content: \"\\e031\";\n}\n.glyphicon-list-alt:before {\n  content: \"\\e032\";\n}\n.glyphicon-lock:before {\n  content: \"\\e033\";\n}\n.glyphicon-flag:before {\n  content: \"\\e034\";\n}\n.glyphicon-headphones:before {\n  content: \"\\e035\";\n}\n.glyphicon-volume-off:before {\n  content: \"\\e036\";\n}\n.glyphicon-volume-down:before {\n  content: \"\\e037\";\n}\n.glyphicon-volume-up:before {\n  content: \"\\e038\";\n}\n.glyphicon-qrcode:before {\n  content: \"\\e039\";\n}\n.glyphicon-barcode:before {\n  content: \"\\e040\";\n}\n.glyphicon-tag:before {\n  content: \"\\e041\";\n}\n.glyphicon-tags:before {\n  content: \"\\e042\";\n}\n.glyphicon-book:before {\n  content: \"\\e043\";\n}\n.glyphicon-bookmark:before {\n  content: \"\\e044\";\n}\n.glyphicon-print:before {\n  content: \"\\e045\";\n}\n.glyphicon-camera:before {\n  content: \"\\e046\";\n}\n.glyphicon-font:before {\n  content: \"\\e047\";\n}\n.glyphicon-bold:before {\n  content: \"\\e048\";\n}\n.glyphicon-italic:before {\n  content: \"\\e049\";\n}\n.glyphicon-text-height:before {\n  content: \"\\e050\";\n}\n.glyphicon-text-width:before {\n  content: \"\\e051\";\n}\n.glyphicon-align-left:before {\n  content: \"\\e052\";\n}\n.glyphicon-align-center:before {\n  content: \"\\e053\";\n}\n.glyphicon-align-right:before {\n  content: \"\\e054\";\n}\n.glyphicon-align-justify:before {\n  content: \"\\e055\";\n}\n.glyphicon-list:before {\n  content: \"\\e056\";\n}\n.glyphicon-indent-left:before {\n  content: \"\\e057\";\n}\n.glyphicon-indent-right:before {\n  content: \"\\e058\";\n}\n.glyphicon-facetime-video:before {\n  content: \"\\e059\";\n}\n.glyphicon-picture:before {\n  content: \"\\e060\";\n}\n.glyphicon-map-marker:before {\n  content: \"\\e062\";\n}\n.glyphicon-adjust:before {\n  content: \"\\e063\";\n}\n.glyphicon-tint:before {\n  content: \"\\e064\";\n}\n.glyphicon-edit:before {\n  content: \"\\e065\";\n}\n.glyphicon-share:before {\n  content: \"\\e066\";\n}\n.glyphicon-check:before {\n  content: \"\\e067\";\n}\n.glyphicon-move:before {\n  content: \"\\e068\";\n}\n.glyphicon-step-backward:before {\n  content: \"\\e069\";\n}\n.glyphicon-fast-backward:before {\n  content: \"\\e070\";\n}\n.glyphicon-backward:before {\n  content: \"\\e071\";\n}\n.glyphicon-play:before {\n  content: \"\\e072\";\n}\n.glyphicon-pause:before {\n  content: \"\\e073\";\n}\n.glyphicon-stop:before {\n  content: \"\\e074\";\n}\n.glyphicon-forward:before {\n  content: \"\\e075\";\n}\n.glyphicon-fast-forward:before {\n  content: \"\\e076\";\n}\n.glyphicon-step-forward:before {\n  content: \"\\e077\";\n}\n.glyphicon-eject:before {\n  content: \"\\e078\";\n}\n.glyphicon-chevron-left:before {\n  content: \"\\e079\";\n}\n.glyphicon-chevron-right:before {\n  content: \"\\e080\";\n}\n.glyphicon-plus-sign:before {\n  content: \"\\e081\";\n}\n.glyphicon-minus-sign:before {\n  content: \"\\e082\";\n}\n.glyphicon-remove-sign:before {\n  content: \"\\e083\";\n}\n.glyphicon-ok-sign:before {\n  content: \"\\e084\";\n}\n.glyphicon-question-sign:before {\n  content: \"\\e085\";\n}\n.glyphicon-info-sign:before {\n  content: \"\\e086\";\n}\n.glyphicon-screenshot:before {\n  content: \"\\e087\";\n}\n.glyphicon-remove-circle:before {\n  content: \"\\e088\";\n}\n.glyphicon-ok-circle:before {\n  content: \"\\e089\";\n}\n.glyphicon-ban-circle:before {\n  content: \"\\e090\";\n}\n.glyphicon-arrow-left:before {\n  content: \"\\e091\";\n}\n.glyphicon-arrow-right:before {\n  content: \"\\e092\";\n}\n.glyphicon-arrow-up:before {\n  content: \"\\e093\";\n}\n.glyphicon-arrow-down:before {\n  content: \"\\e094\";\n}\n.glyphicon-share-alt:before {\n  content: \"\\e095\";\n}\n.glyphicon-resize-full:before {\n  content: \"\\e096\";\n}\n.glyphicon-resize-small:before {\n  content: \"\\e097\";\n}\n.glyphicon-exclamation-sign:before {\n  content: \"\\e101\";\n}\n.glyphicon-gift:before {\n  content: \"\\e102\";\n}\n.glyphicon-leaf:before {\n  content: \"\\e103\";\n}\n.glyphicon-fire:before {\n  content: \"\\e104\";\n}\n.glyphicon-eye-open:before {\n  content: \"\\e105\";\n}\n.glyphicon-eye-close:before {\n  content: \"\\e106\";\n}\n.glyphicon-warning-sign:before {\n  content: \"\\e107\";\n}\n.glyphicon-plane:before {\n  content: \"\\e108\";\n}\n.glyphicon-calendar:before {\n  content: \"\\e109\";\n}\n.glyphicon-random:before {\n  content: \"\\e110\";\n}\n.glyphicon-comment:before {\n  content: \"\\e111\";\n}\n.glyphicon-magnet:before {\n  content: \"\\e112\";\n}\n.glyphicon-chevron-up:before {\n  content: \"\\e113\";\n}\n.glyphicon-chevron-down:before {\n  content: \"\\e114\";\n}\n.glyphicon-retweet:before {\n  content: \"\\e115\";\n}\n.glyphicon-shopping-cart:before {\n  content: \"\\e116\";\n}\n.glyphicon-folder-close:before {\n  content: \"\\e117\";\n}\n.glyphicon-folder-open:before {\n  content: \"\\e118\";\n}\n.glyphicon-resize-vertical:before {\n  content: \"\\e119\";\n}\n.glyphicon-resize-horizontal:before {\n  content: \"\\e120\";\n}\n.glyphicon-hdd:before {\n  content: \"\\e121\";\n}\n.glyphicon-bullhorn:before {\n  content: \"\\e122\";\n}\n.glyphicon-bell:before {\n  content: \"\\e123\";\n}\n.glyphicon-certificate:before {\n  content: \"\\e124\";\n}\n.glyphicon-thumbs-up:before {\n  content: \"\\e125\";\n}\n.glyphicon-thumbs-down:before {\n  content: \"\\e126\";\n}\n.glyphicon-hand-right:before {\n  content: \"\\e127\";\n}\n.glyphicon-hand-left:before {\n  content: \"\\e128\";\n}\n.glyphicon-hand-up:before {\n  content: \"\\e129\";\n}\n.glyphicon-hand-down:before {\n  content: \"\\e130\";\n}\n.glyphicon-circle-arrow-right:before {\n  content: \"\\e131\";\n}\n.glyphicon-circle-arrow-left:before {\n  content: \"\\e132\";\n}\n.glyphicon-circle-arrow-up:before {\n  content: \"\\e133\";\n}\n.glyphicon-circle-arrow-down:before {\n  content: \"\\e134\";\n}\n.glyphicon-globe:before {\n  content: \"\\e135\";\n}\n.glyphicon-wrench:before {\n  content: \"\\e136\";\n}\n.glyphicon-tasks:before {\n  content: \"\\e137\";\n}\n.glyphicon-filter:before {\n  content: \"\\e138\";\n}\n.glyphicon-briefcase:before {\n  content: \"\\e139\";\n}\n.glyphicon-fullscreen:before {\n  content: \"\\e140\";\n}\n.glyphicon-dashboard:before {\n  content: \"\\e141\";\n}\n.glyphicon-paperclip:before {\n  content: \"\\e142\";\n}\n.glyphicon-heart-empty:before {\n  content: \"\\e143\";\n}\n.glyphicon-link:before {\n  content: \"\\e144\";\n}\n.glyphicon-phone:before {\n  content: \"\\e145\";\n}\n.glyphicon-pushpin:before {\n  content: \"\\e146\";\n}\n.glyphicon-usd:before {\n  content: \"\\e148\";\n}\n.glyphicon-gbp:before {\n  content: \"\\e149\";\n}\n.glyphicon-sort:before {\n  content: \"\\e150\";\n}\n.glyphicon-sort-by-alphabet:before {\n  content: \"\\e151\";\n}\n.glyphicon-sort-by-alphabet-alt:before {\n  content: \"\\e152\";\n}\n.glyphicon-sort-by-order:before {\n  content: \"\\e153\";\n}\n.glyphicon-sort-by-order-alt:before {\n  content: \"\\e154\";\n}\n.glyphicon-sort-by-attributes:before {\n  content: \"\\e155\";\n}\n.glyphicon-sort-by-attributes-alt:before {\n  content: \"\\e156\";\n}\n.glyphicon-unchecked:before {\n  content: \"\\e157\";\n}\n.glyphicon-expand:before {\n  content: \"\\e158\";\n}\n.glyphicon-collapse-down:before {\n  content: \"\\e159\";\n}\n.glyphicon-collapse-up:before {\n  content: \"\\e160\";\n}\n.glyphicon-log-in:before {\n  content: \"\\e161\";\n}\n.glyphicon-flash:before {\n  content: \"\\e162\";\n}\n.glyphicon-log-out:before {\n  content: \"\\e163\";\n}\n.glyphicon-new-window:before {\n  content: \"\\e164\";\n}\n.glyphicon-record:before {\n  content: \"\\e165\";\n}\n.glyphicon-save:before {\n  content: \"\\e166\";\n}\n.glyphicon-open:before {\n  content: \"\\e167\";\n}\n.glyphicon-saved:before {\n  content: \"\\e168\";\n}\n.glyphicon-import:before {\n  content: \"\\e169\";\n}\n.glyphicon-export:before {\n  content: \"\\e170\";\n}\n.glyphicon-send:before {\n  content: \"\\e171\";\n}\n.glyphicon-floppy-disk:before {\n  content: \"\\e172\";\n}\n.glyphicon-floppy-saved:before {\n  content: \"\\e173\";\n}\n.glyphicon-floppy-remove:before {\n  content: \"\\e174\";\n}\n.glyphicon-floppy-save:before {\n  content: \"\\e175\";\n}\n.glyphicon-floppy-open:before {\n  content: \"\\e176\";\n}\n.glyphicon-credit-card:before {\n  content: \"\\e177\";\n}\n.glyphicon-transfer:before {\n  content: \"\\e178\";\n}\n.glyphicon-cutlery:before {\n  content: \"\\e179\";\n}\n.glyphicon-header:before {\n  content: \"\\e180\";\n}\n.glyphicon-compressed:before {\n  content: \"\\e181\";\n}\n.glyphicon-earphone:before {\n  content: \"\\e182\";\n}\n.glyphicon-phone-alt:before {\n  content: \"\\e183\";\n}\n.glyphicon-tower:before {\n  content: \"\\e184\";\n}\n.glyphicon-stats:before {\n  content: \"\\e185\";\n}\n.glyphicon-sd-video:before {\n  content: \"\\e186\";\n}\n.glyphicon-hd-video:before {\n  content: \"\\e187\";\n}\n.glyphicon-subtitles:before {\n  content: \"\\e188\";\n}\n.glyphicon-sound-stereo:before {\n  content: \"\\e189\";\n}\n.glyphicon-sound-dolby:before {\n  content: \"\\e190\";\n}\n.glyphicon-sound-5-1:before {\n  content: \"\\e191\";\n}\n.glyphicon-sound-6-1:before {\n  content: \"\\e192\";\n}\n.glyphicon-sound-7-1:before {\n  content: \"\\e193\";\n}\n.glyphicon-copyright-mark:before {\n  content: \"\\e194\";\n}\n.glyphicon-registration-mark:before {\n  content: \"\\e195\";\n}\n.glyphicon-cloud-download:before {\n  content: \"\\e197\";\n}\n.glyphicon-cloud-upload:before {\n  content: \"\\e198\";\n}\n.glyphicon-tree-conifer:before {\n  content: \"\\e199\";\n}\n.glyphicon-tree-deciduous:before {\n  content: \"\\e200\";\n}\n.glyphicon-cd:before {\n  content: \"\\e201\";\n}\n.glyphicon-save-file:before {\n  content: \"\\e202\";\n}\n.glyphicon-open-file:before {\n  content: \"\\e203\";\n}\n.glyphicon-level-up:before {\n  content: \"\\e204\";\n}\n.glyphicon-copy:before {\n  content: \"\\e205\";\n}\n.glyphicon-paste:before {\n  content: \"\\e206\";\n}\n.glyphicon-alert:before {\n  content: \"\\e209\";\n}\n.glyphicon-equalizer:before {\n  content: \"\\e210\";\n}\n.glyphicon-king:before {\n  content: \"\\e211\";\n}\n.glyphicon-queen:before {\n  content: \"\\e212\";\n}\n.glyphicon-pawn:before {\n  content: \"\\e213\";\n}\n.glyphicon-bishop:before {\n  content: \"\\e214\";\n}\n.glyphicon-knight:before {\n  content: \"\\e215\";\n}\n.glyphicon-baby-formula:before {\n  content: \"\\e216\";\n}\n.glyphicon-tent:before {\n  content: \"\\26fa\";\n}\n.glyphicon-blackboard:before {\n  content: \"\\e218\";\n}\n.glyphicon-bed:before {\n  content: \"\\e219\";\n}\n.glyphicon-apple:before {\n  content: \"\\f8ff\";\n}\n.glyphicon-erase:before {\n  content: \"\\e221\";\n}\n.glyphicon-hourglass:before {\n  content: \"\\231b\";\n}\n.glyphicon-lamp:before {\n  content: \"\\e223\";\n}\n.glyphicon-duplicate:before {\n  content: \"\\e224\";\n}\n.glyphicon-piggy-bank:before {\n  content: \"\\e225\";\n}\n.glyphicon-scissors:before {\n  content: \"\\e226\";\n}\n.glyphicon-bitcoin:before {\n  content: \"\\e227\";\n}\n.glyphicon-yen:before {\n  content: \"\\00a5\";\n}\n.glyphicon-ruble:before {\n  content: \"\\20bd\";\n}\n.glyphicon-scale:before {\n  content: \"\\e230\";\n}\n.glyphicon-ice-lolly:before {\n  content: \"\\e231\";\n}\n.glyphicon-ice-lolly-tasted:before {\n  content: \"\\e232\";\n}\n.glyphicon-education:before {\n  content: \"\\e233\";\n}\n.glyphicon-option-horizontal:before {\n  content: \"\\e234\";\n}\n.glyphicon-option-vertical:before {\n  content: \"\\e235\";\n}\n.glyphicon-menu-hamburger:before {\n  content: \"\\e236\";\n}\n.glyphicon-modal-window:before {\n  content: \"\\e237\";\n}\n.glyphicon-oil:before {\n  content: \"\\e238\";\n}\n.glyphicon-grain:before {\n  content: \"\\e239\";\n}\n.glyphicon-sunglasses:before {\n  content: \"\\e240\";\n}\n.glyphicon-text-size:before {\n  content: \"\\e241\";\n}\n.glyphicon-text-color:before {\n  content: \"\\e242\";\n}\n.glyphicon-text-background:before {\n  content: \"\\e243\";\n}\n.glyphicon-object-align-top:before {\n  content: \"\\e244\";\n}\n.glyphicon-object-align-bottom:before {\n  content: \"\\e245\";\n}\n.glyphicon-object-align-horizontal:before {\n  content: \"\\e246\";\n}\n.glyphicon-object-align-left:before {\n  content: \"\\e247\";\n}\n.glyphicon-object-align-vertical:before {\n  content: \"\\e248\";\n}\n.glyphicon-object-align-right:before {\n  content: \"\\e249\";\n}\n.glyphicon-triangle-right:before {\n  content: \"\\e250\";\n}\n.glyphicon-triangle-left:before {\n  content: \"\\e251\";\n}\n.glyphicon-triangle-bottom:before {\n  content: \"\\e252\";\n}\n.glyphicon-triangle-top:before {\n  content: \"\\e253\";\n}\n.glyphicon-console:before {\n  content: \"\\e254\";\n}\n.glyphicon-superscript:before {\n  content: \"\\e255\";\n}\n.glyphicon-subscript:before {\n  content: \"\\e256\";\n}\n.glyphicon-menu-left:before {\n  content: \"\\e257\";\n}\n.glyphicon-menu-right:before {\n  content: \"\\e258\";\n}\n.glyphicon-menu-down:before {\n  content: \"\\e259\";\n}\n.glyphicon-menu-up:before {\n  content: \"\\e260\";\n}\n* {\n  -webkit-box-sizing: border-box;\n     -moz-box-sizing: border-box;\n          box-sizing: border-box;\n}\n*:before,\n*:after {\n  -webkit-box-sizing: border-box;\n     -moz-box-sizing: border-box;\n          box-sizing: border-box;\n}\nhtml {\n  font-size: 10px;\n\n  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\nbody {\n  font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  font-size: 14px;\n  line-height: 1.42857143;\n  color: #333;\n  background-color: #fff;\n}\ninput,\nbutton,\nselect,\ntextarea {\n  font-family: inherit;\n  font-size: inherit;\n  line-height: inherit;\n}\na {\n  color: #337ab7;\n  text-decoration: none;\n}\na:hover,\na:focus {\n  color: #23527c;\n  text-decoration: underline;\n}\na:focus {\n  outline: thin dotted;\n  outline: 5px auto -webkit-focus-ring-color;\n  outline-offset: -2px;\n}\nfigure {\n  margin: 0;\n}\nimg {\n  vertical-align: middle;\n}\n.img-responsive,\n.thumbnail > img,\n.thumbnail a > img,\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n  display: block;\n  max-width: 100%;\n  height: auto;\n}\n.img-rounded {\n  border-radius: 6px;\n}\n.img-thumbnail {\n  display: inline-block;\n  max-width: 100%;\n  height: auto;\n  padding: 4px;\n  line-height: 1.42857143;\n  background-color: #fff;\n  border: 1px solid #ddd;\n  border-radius: 4px;\n  -webkit-transition: all .2s ease-in-out;\n       -o-transition: all .2s ease-in-out;\n          transition: all .2s ease-in-out;\n}\n.img-circle {\n  border-radius: 50%;\n}\nhr {\n  margin-top: 20px;\n  margin-bottom: 20px;\n  border: 0;\n  border-top: 1px solid #eee;\n}\n.sr-only {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  padding: 0;\n  margin: -1px;\n  overflow: hidden;\n  clip: rect(0, 0, 0, 0);\n  border: 0;\n}\n.sr-only-focusable:active,\n.sr-only-focusable:focus {\n  position: static;\n  width: auto;\n  height: auto;\n  margin: 0;\n  overflow: visible;\n  clip: auto;\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\n.h1,\n.h2,\n.h3,\n.h4,\n.h5,\n.h6 {\n  font-family: inherit;\n  font-weight: 500;\n  line-height: 1.1;\n  color: inherit;\n}\nh1 small,\nh2 small,\nh3 small,\nh4 small,\nh5 small,\nh6 small,\n.h1 small,\n.h2 small,\n.h3 small,\n.h4 small,\n.h5 small,\n.h6 small,\nh1 .small,\nh2 .small,\nh3 .small,\nh4 .small,\nh5 .small,\nh6 .small,\n.h1 .small,\n.h2 .small,\n.h3 .small,\n.h4 .small,\n.h5 .small,\n.h6 .small {\n  font-weight: normal;\n  line-height: 1;\n  color: #777;\n}\nh1,\n.h1,\nh2,\n.h2,\nh3,\n.h3 {\n  margin-top: 20px;\n  margin-bottom: 10px;\n}\nh1 small,\n.h1 small,\nh2 small,\n.h2 small,\nh3 small,\n.h3 small,\nh1 .small,\n.h1 .small,\nh2 .small,\n.h2 .small,\nh3 .small,\n.h3 .small {\n  font-size: 65%;\n}\nh4,\n.h4,\nh5,\n.h5,\nh6,\n.h6 {\n  margin-top: 10px;\n  margin-bottom: 10px;\n}\nh4 small,\n.h4 small,\nh5 small,\n.h5 small,\nh6 small,\n.h6 small,\nh4 .small,\n.h4 .small,\nh5 .small,\n.h5 .small,\nh6 .small,\n.h6 .small {\n  font-size: 75%;\n}\nh1,\n.h1 {\n  font-size: 36px;\n}\nh2,\n.h2 {\n  font-size: 30px;\n}\nh3,\n.h3 {\n  font-size: 24px;\n}\nh4,\n.h4 {\n  font-size: 18px;\n}\nh5,\n.h5 {\n  font-size: 14px;\n}\nh6,\n.h6 {\n  font-size: 12px;\n}\np {\n  margin: 0 0 10px;\n}\n.lead {\n  margin-bottom: 20px;\n  font-size: 16px;\n  font-weight: 300;\n  line-height: 1.4;\n}\n@media (min-width: 768px) {\n  .lead {\n    font-size: 21px;\n  }\n}\nsmall,\n.small {\n  font-size: 85%;\n}\nmark,\n.mark {\n  padding: .2em;\n  background-color: #fcf8e3;\n}\n.text-left {\n  text-align: left;\n}\n.text-right {\n  text-align: right;\n}\n.text-center {\n  text-align: center;\n}\n.text-justify {\n  text-align: justify;\n}\n.text-nowrap {\n  white-space: nowrap;\n}\n.text-lowercase {\n  text-transform: lowercase;\n}\n.text-uppercase {\n  text-transform: uppercase;\n}\n.text-capitalize {\n  text-transform: capitalize;\n}\n.text-muted {\n  color: #777;\n}\n.text-primary {\n  color: #337ab7;\n}\na.text-primary:hover {\n  color: #286090;\n}\n.text-success {\n  color: #3c763d;\n}\na.text-success:hover {\n  color: #2b542c;\n}\n.text-info {\n  color: #31708f;\n}\na.text-info:hover {\n  color: #245269;\n}\n.text-warning {\n  color: #8a6d3b;\n}\na.text-warning:hover {\n  color: #66512c;\n}\n.text-danger {\n  color: #a94442;\n}\na.text-danger:hover {\n  color: #843534;\n}\n.bg-primary {\n  color: #fff;\n  background-color: #337ab7;\n}\na.bg-primary:hover {\n  background-color: #286090;\n}\n.bg-success {\n  background-color: #dff0d8;\n}\na.bg-success:hover {\n  background-color: #c1e2b3;\n}\n.bg-info {\n  background-color: #d9edf7;\n}\na.bg-info:hover {\n  background-color: #afd9ee;\n}\n.bg-warning {\n  background-color: #fcf8e3;\n}\na.bg-warning:hover {\n  background-color: #f7ecb5;\n}\n.bg-danger {\n  background-color: #f2dede;\n}\na.bg-danger:hover {\n  background-color: #e4b9b9;\n}\n.page-header {\n  padding-bottom: 9px;\n  margin: 40px 0 20px;\n  border-bottom: 1px solid #eee;\n}\nul,\nol {\n  margin-top: 0;\n  margin-bottom: 10px;\n}\nul ul,\nol ul,\nul ol,\nol ol {\n  margin-bottom: 0;\n}\n.list-unstyled {\n  padding-left: 0;\n  list-style: none;\n}\n.list-inline {\n  padding-left: 0;\n  margin-left: -5px;\n  list-style: none;\n}\n.list-inline > li {\n  display: inline-block;\n  padding-right: 5px;\n  padding-left: 5px;\n}\ndl {\n  margin-top: 0;\n  margin-bottom: 20px;\n}\ndt,\ndd {\n  line-height: 1.42857143;\n}\ndt {\n  font-weight: bold;\n}\ndd {\n  margin-left: 0;\n}\n@media (min-width: 768px) {\n  .dl-horizontal dt {\n    float: left;\n    width: 160px;\n    overflow: hidden;\n    clear: left;\n    text-align: right;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n  .dl-horizontal dd {\n    margin-left: 180px;\n  }\n}\nabbr[title],\nabbr[data-original-title] {\n  cursor: help;\n  border-bottom: 1px dotted #777;\n}\n.initialism {\n  font-size: 90%;\n  text-transform: uppercase;\n}\nblockquote {\n  padding: 10px 20px;\n  margin: 0 0 20px;\n  font-size: 17.5px;\n  border-left: 5px solid #eee;\n}\nblockquote p:last-child,\nblockquote ul:last-child,\nblockquote ol:last-child {\n  margin-bottom: 0;\n}\nblockquote footer,\nblockquote small,\nblockquote .small {\n  display: block;\n  font-size: 80%;\n  line-height: 1.42857143;\n  color: #777;\n}\nblockquote footer:before,\nblockquote small:before,\nblockquote .small:before {\n  content: '\\2014 \\00A0';\n}\n.blockquote-reverse,\nblockquote.pull-right {\n  padding-right: 15px;\n  padding-left: 0;\n  text-align: right;\n  border-right: 5px solid #eee;\n  border-left: 0;\n}\n.blockquote-reverse footer:before,\nblockquote.pull-right footer:before,\n.blockquote-reverse small:before,\nblockquote.pull-right small:before,\n.blockquote-reverse .small:before,\nblockquote.pull-right .small:before {\n  content: '';\n}\n.blockquote-reverse footer:after,\nblockquote.pull-right footer:after,\n.blockquote-reverse small:after,\nblockquote.pull-right small:after,\n.blockquote-reverse .small:after,\nblockquote.pull-right .small:after {\n  content: '\\00A0 \\2014';\n}\naddress {\n  margin-bottom: 20px;\n  font-style: normal;\n  line-height: 1.42857143;\n}\ncode,\nkbd,\npre,\nsamp {\n  font-family: Menlo, Monaco, Consolas, \"Courier New\", monospace;\n}\ncode {\n  padding: 2px 4px;\n  font-size: 90%;\n  color: #c7254e;\n  background-color: #f9f2f4;\n  border-radius: 4px;\n}\nkbd {\n  padding: 2px 4px;\n  font-size: 90%;\n  color: #fff;\n  background-color: #333;\n  border-radius: 3px;\n  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25);\n          box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25);\n}\nkbd kbd {\n  padding: 0;\n  font-size: 100%;\n  font-weight: bold;\n  -webkit-box-shadow: none;\n          box-shadow: none;\n}\npre {\n  display: block;\n  padding: 9.5px;\n  margin: 0 0 10px;\n  font-size: 13px;\n  line-height: 1.42857143;\n  color: #333;\n  word-break: break-all;\n  word-wrap: break-word;\n  background-color: #f5f5f5;\n  border: 1px solid #ccc;\n  border-radius: 4px;\n}\npre code {\n  padding: 0;\n  font-size: inherit;\n  color: inherit;\n  white-space: pre-wrap;\n  background-color: transparent;\n  border-radius: 0;\n}\n.pre-scrollable {\n  max-height: 340px;\n  overflow-y: scroll;\n}\n.container {\n  padding-right: 15px;\n  padding-left: 15px;\n  margin-right: auto;\n  margin-left: auto;\n}\n@media (min-width: 768px) {\n  .container {\n    width: 750px;\n  }\n}\n@media (min-width: 992px) {\n  .container {\n    width: 970px;\n  }\n}\n@media (min-width: 1200px) {\n  .container {\n    width: 1170px;\n  }\n}\n.container-fluid {\n  padding-right: 15px;\n  padding-left: 15px;\n  margin-right: auto;\n  margin-left: auto;\n}\n.row {\n  margin-right: -15px;\n  margin-left: -15px;\n}\n.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {\n  position: relative;\n  min-height: 1px;\n  padding-right: 15px;\n  padding-left: 15px;\n}\n.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 {\n  float: left;\n}\n.col-xs-12 {\n  width: 100%;\n}\n.col-xs-11 {\n  width: 91.66666667%;\n}\n.col-xs-10 {\n  width: 83.33333333%;\n}\n.col-xs-9 {\n  width: 75%;\n}\n.col-xs-8 {\n  width: 66.66666667%;\n}\n.col-xs-7 {\n  width: 58.33333333%;\n}\n.col-xs-6 {\n  width: 50%;\n}\n.col-xs-5 {\n  width: 41.66666667%;\n}\n.col-xs-4 {\n  width: 33.33333333%;\n}\n.col-xs-3 {\n  width: 25%;\n}\n.col-xs-2 {\n  width: 16.66666667%;\n}\n.col-xs-1 {\n  width: 8.33333333%;\n}\n.col-xs-pull-12 {\n  right: 100%;\n}\n.col-xs-pull-11 {\n  right: 91.66666667%;\n}\n.col-xs-pull-10 {\n  right: 83.33333333%;\n}\n.col-xs-pull-9 {\n  right: 75%;\n}\n.col-xs-pull-8 {\n  right: 66.66666667%;\n}\n.col-xs-pull-7 {\n  right: 58.33333333%;\n}\n.col-xs-pull-6 {\n  right: 50%;\n}\n.col-xs-pull-5 {\n  right: 41.66666667%;\n}\n.col-xs-pull-4 {\n  right: 33.33333333%;\n}\n.col-xs-pull-3 {\n  right: 25%;\n}\n.col-xs-pull-2 {\n  right: 16.66666667%;\n}\n.col-xs-pull-1 {\n  right: 8.33333333%;\n}\n.col-xs-pull-0 {\n  right: auto;\n}\n.col-xs-push-12 {\n  left: 100%;\n}\n.col-xs-push-11 {\n  left: 91.66666667%;\n}\n.col-xs-push-10 {\n  left: 83.33333333%;\n}\n.col-xs-push-9 {\n  left: 75%;\n}\n.col-xs-push-8 {\n  left: 66.66666667%;\n}\n.col-xs-push-7 {\n  left: 58.33333333%;\n}\n.col-xs-push-6 {\n  left: 50%;\n}\n.col-xs-push-5 {\n  left: 41.66666667%;\n}\n.col-xs-push-4 {\n  left: 33.33333333%;\n}\n.col-xs-push-3 {\n  left: 25%;\n}\n.col-xs-push-2 {\n  left: 16.66666667%;\n}\n.col-xs-push-1 {\n  left: 8.33333333%;\n}\n.col-xs-push-0 {\n  left: auto;\n}\n.col-xs-offset-12 {\n  margin-left: 100%;\n}\n.col-xs-offset-11 {\n  margin-left: 91.66666667%;\n}\n.col-xs-offset-10 {\n  margin-left: 83.33333333%;\n}\n.col-xs-offset-9 {\n  margin-left: 75%;\n}\n.col-xs-offset-8 {\n  margin-left: 66.66666667%;\n}\n.col-xs-offset-7 {\n  margin-left: 58.33333333%;\n}\n.col-xs-offset-6 {\n  margin-left: 50%;\n}\n.col-xs-offset-5 {\n  margin-left: 41.66666667%;\n}\n.col-xs-offset-4 {\n  margin-left: 33.33333333%;\n}\n.col-xs-offset-3 {\n  margin-left: 25%;\n}\n.col-xs-offset-2 {\n  margin-left: 16.66666667%;\n}\n.col-xs-offset-1 {\n  margin-left: 8.33333333%;\n}\n.col-xs-offset-0 {\n  margin-left: 0;\n}\n@media (min-width: 768px) {\n  .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 {\n    float: left;\n  }\n  .col-sm-12 {\n    width: 100%;\n  }\n  .col-sm-11 {\n    width: 91.66666667%;\n  }\n  .col-sm-10 {\n    width: 83.33333333%;\n  }\n  .col-sm-9 {\n    width: 75%;\n  }\n  .col-sm-8 {\n    width: 66.66666667%;\n  }\n  .col-sm-7 {\n    width: 58.33333333%;\n  }\n  .col-sm-6 {\n    width: 50%;\n  }\n  .col-sm-5 {\n    width: 41.66666667%;\n  }\n  .col-sm-4 {\n    width: 33.33333333%;\n  }\n  .col-sm-3 {\n    width: 25%;\n  }\n  .col-sm-2 {\n    width: 16.66666667%;\n  }\n  .col-sm-1 {\n    width: 8.33333333%;\n  }\n  .col-sm-pull-12 {\n    right: 100%;\n  }\n  .col-sm-pull-11 {\n    right: 91.66666667%;\n  }\n  .col-sm-pull-10 {\n    right: 83.33333333%;\n  }\n  .col-sm-pull-9 {\n    right: 75%;\n  }\n  .col-sm-pull-8 {\n    right: 66.66666667%;\n  }\n  .col-sm-pull-7 {\n    right: 58.33333333%;\n  }\n  .col-sm-pull-6 {\n    right: 50%;\n  }\n  .col-sm-pull-5 {\n    right: 41.66666667%;\n  }\n  .col-sm-pull-4 {\n    right: 33.33333333%;\n  }\n  .col-sm-pull-3 {\n    right: 25%;\n  }\n  .col-sm-pull-2 {\n    right: 16.66666667%;\n  }\n  .col-sm-pull-1 {\n    right: 8.33333333%;\n  }\n  .col-sm-pull-0 {\n    right: auto;\n  }\n  .col-sm-push-12 {\n    left: 100%;\n  }\n  .col-sm-push-11 {\n    left: 91.66666667%;\n  }\n  .col-sm-push-10 {\n    left: 83.33333333%;\n  }\n  .col-sm-push-9 {\n    left: 75%;\n  }\n  .col-sm-push-8 {\n    left: 66.66666667%;\n  }\n  .col-sm-push-7 {\n    left: 58.33333333%;\n  }\n  .col-sm-push-6 {\n    left: 50%;\n  }\n  .col-sm-push-5 {\n    left: 41.66666667%;\n  }\n  .col-sm-push-4 {\n    left: 33.33333333%;\n  }\n  .col-sm-push-3 {\n    left: 25%;\n  }\n  .col-sm-push-2 {\n    left: 16.66666667%;\n  }\n  .col-sm-push-1 {\n    left: 8.33333333%;\n  }\n  .col-sm-push-0 {\n    left: auto;\n  }\n  .col-sm-offset-12 {\n    margin-left: 100%;\n  }\n  .col-sm-offset-11 {\n    margin-left: 91.66666667%;\n  }\n  .col-sm-offset-10 {\n    margin-left: 83.33333333%;\n  }\n  .col-sm-offset-9 {\n    margin-left: 75%;\n  }\n  .col-sm-offset-8 {\n    margin-left: 66.66666667%;\n  }\n  .col-sm-offset-7 {\n    margin-left: 58.33333333%;\n  }\n  .col-sm-offset-6 {\n    margin-left: 50%;\n  }\n  .col-sm-offset-5 {\n    margin-left: 41.66666667%;\n  }\n  .col-sm-offset-4 {\n    margin-left: 33.33333333%;\n  }\n  .col-sm-offset-3 {\n    margin-left: 25%;\n  }\n  .col-sm-offset-2 {\n    margin-left: 16.66666667%;\n  }\n  .col-sm-offset-1 {\n    margin-left: 8.33333333%;\n  }\n  .col-sm-offset-0 {\n    margin-left: 0;\n  }\n}\n@media (min-width: 992px) {\n  .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 {\n    float: left;\n  }\n  .col-md-12 {\n    width: 100%;\n  }\n  .col-md-11 {\n    width: 91.66666667%;\n  }\n  .col-md-10 {\n    width: 83.33333333%;\n  }\n  .col-md-9 {\n    width: 75%;\n  }\n  .col-md-8 {\n    width: 66.66666667%;\n  }\n  .col-md-7 {\n    width: 58.33333333%;\n  }\n  .col-md-6 {\n    width: 50%;\n  }\n  .col-md-5 {\n    width: 41.66666667%;\n  }\n  .col-md-4 {\n    width: 33.33333333%;\n  }\n  .col-md-3 {\n    width: 25%;\n  }\n  .col-md-2 {\n    width: 16.66666667%;\n  }\n  .col-md-1 {\n    width: 8.33333333%;\n  }\n  .col-md-pull-12 {\n    right: 100%;\n  }\n  .col-md-pull-11 {\n    right: 91.66666667%;\n  }\n  .col-md-pull-10 {\n    right: 83.33333333%;\n  }\n  .col-md-pull-9 {\n    right: 75%;\n  }\n  .col-md-pull-8 {\n    right: 66.66666667%;\n  }\n  .col-md-pull-7 {\n    right: 58.33333333%;\n  }\n  .col-md-pull-6 {\n    right: 50%;\n  }\n  .col-md-pull-5 {\n    right: 41.66666667%;\n  }\n  .col-md-pull-4 {\n    right: 33.33333333%;\n  }\n  .col-md-pull-3 {\n    right: 25%;\n  }\n  .col-md-pull-2 {\n    right: 16.66666667%;\n  }\n  .col-md-pull-1 {\n    right: 8.33333333%;\n  }\n  .col-md-pull-0 {\n    right: auto;\n  }\n  .col-md-push-12 {\n    left: 100%;\n  }\n  .col-md-push-11 {\n    left: 91.66666667%;\n  }\n  .col-md-push-10 {\n    left: 83.33333333%;\n  }\n  .col-md-push-9 {\n    left: 75%;\n  }\n  .col-md-push-8 {\n    left: 66.66666667%;\n  }\n  .col-md-push-7 {\n    left: 58.33333333%;\n  }\n  .col-md-push-6 {\n    left: 50%;\n  }\n  .col-md-push-5 {\n    left: 41.66666667%;\n  }\n  .col-md-push-4 {\n    left: 33.33333333%;\n  }\n  .col-md-push-3 {\n    left: 25%;\n  }\n  .col-md-push-2 {\n    left: 16.66666667%;\n  }\n  .col-md-push-1 {\n    left: 8.33333333%;\n  }\n  .col-md-push-0 {\n    left: auto;\n  }\n  .col-md-offset-12 {\n    margin-left: 100%;\n  }\n  .col-md-offset-11 {\n    margin-left: 91.66666667%;\n  }\n  .col-md-offset-10 {\n    margin-left: 83.33333333%;\n  }\n  .col-md-offset-9 {\n    margin-left: 75%;\n  }\n  .col-md-offset-8 {\n    margin-left: 66.66666667%;\n  }\n  .col-md-offset-7 {\n    margin-left: 58.33333333%;\n  }\n  .col-md-offset-6 {\n    margin-left: 50%;\n  }\n  .col-md-offset-5 {\n    margin-left: 41.66666667%;\n  }\n  .col-md-offset-4 {\n    margin-left: 33.33333333%;\n  }\n  .col-md-offset-3 {\n    margin-left: 25%;\n  }\n  .col-md-offset-2 {\n    margin-left: 16.66666667%;\n  }\n  .col-md-offset-1 {\n    margin-left: 8.33333333%;\n  }\n  .col-md-offset-0 {\n    margin-left: 0;\n  }\n}\n@media (min-width: 1200px) {\n  .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 {\n    float: left;\n  }\n  .col-lg-12 {\n    width: 100%;\n  }\n  .col-lg-11 {\n    width: 91.66666667%;\n  }\n  .col-lg-10 {\n    width: 83.33333333%;\n  }\n  .col-lg-9 {\n    width: 75%;\n  }\n  .col-lg-8 {\n    width: 66.66666667%;\n  }\n  .col-lg-7 {\n    width: 58.33333333%;\n  }\n  .col-lg-6 {\n    width: 50%;\n  }\n  .col-lg-5 {\n    width: 41.66666667%;\n  }\n  .col-lg-4 {\n    width: 33.33333333%;\n  }\n  .col-lg-3 {\n    width: 25%;\n  }\n  .col-lg-2 {\n    width: 16.66666667%;\n  }\n  .col-lg-1 {\n    width: 8.33333333%;\n  }\n  .col-lg-pull-12 {\n    right: 100%;\n  }\n  .col-lg-pull-11 {\n    right: 91.66666667%;\n  }\n  .col-lg-pull-10 {\n    right: 83.33333333%;\n  }\n  .col-lg-pull-9 {\n    right: 75%;\n  }\n  .col-lg-pull-8 {\n    right: 66.66666667%;\n  }\n  .col-lg-pull-7 {\n    right: 58.33333333%;\n  }\n  .col-lg-pull-6 {\n    right: 50%;\n  }\n  .col-lg-pull-5 {\n    right: 41.66666667%;\n  }\n  .col-lg-pull-4 {\n    right: 33.33333333%;\n  }\n  .col-lg-pull-3 {\n    right: 25%;\n  }\n  .col-lg-pull-2 {\n    right: 16.66666667%;\n  }\n  .col-lg-pull-1 {\n    right: 8.33333333%;\n  }\n  .col-lg-pull-0 {\n    right: auto;\n  }\n  .col-lg-push-12 {\n    left: 100%;\n  }\n  .col-lg-push-11 {\n    left: 91.66666667%;\n  }\n  .col-lg-push-10 {\n    left: 83.33333333%;\n  }\n  .col-lg-push-9 {\n    left: 75%;\n  }\n  .col-lg-push-8 {\n    left: 66.66666667%;\n  }\n  .col-lg-push-7 {\n    left: 58.33333333%;\n  }\n  .col-lg-push-6 {\n    left: 50%;\n  }\n  .col-lg-push-5 {\n    left: 41.66666667%;\n  }\n  .col-lg-push-4 {\n    left: 33.33333333%;\n  }\n  .col-lg-push-3 {\n    left: 25%;\n  }\n  .col-lg-push-2 {\n    left: 16.66666667%;\n  }\n  .col-lg-push-1 {\n    left: 8.33333333%;\n  }\n  .col-lg-push-0 {\n    left: auto;\n  }\n  .col-lg-offset-12 {\n    margin-left: 100%;\n  }\n  .col-lg-offset-11 {\n    margin-left: 91.66666667%;\n  }\n  .col-lg-offset-10 {\n    margin-left: 83.33333333%;\n  }\n  .col-lg-offset-9 {\n    margin-left: 75%;\n  }\n  .col-lg-offset-8 {\n    margin-left: 66.66666667%;\n  }\n  .col-lg-offset-7 {\n    margin-left: 58.33333333%;\n  }\n  .col-lg-offset-6 {\n    margin-left: 50%;\n  }\n  .col-lg-offset-5 {\n    margin-left: 41.66666667%;\n  }\n  .col-lg-offset-4 {\n    margin-left: 33.33333333%;\n  }\n  .col-lg-offset-3 {\n    margin-left: 25%;\n  }\n  .col-lg-offset-2 {\n    margin-left: 16.66666667%;\n  }\n  .col-lg-offset-1 {\n    margin-left: 8.33333333%;\n  }\n  .col-lg-offset-0 {\n    margin-left: 0;\n  }\n}\ntable {\n  background-color: transparent;\n}\ncaption {\n  padding-top: 8px;\n  padding-bottom: 8px;\n  color: #777;\n  text-align: left;\n}\nth {\n  text-align: left;\n}\n.table {\n  width: 100%;\n  max-width: 100%;\n  margin-bottom: 20px;\n}\n.table > thead > tr > th,\n.table > tbody > tr > th,\n.table > tfoot > tr > th,\n.table > thead > tr > td,\n.table > tbody > tr > td,\n.table > tfoot > tr > td {\n  padding: 8px;\n  line-height: 1.42857143;\n  vertical-align: top;\n  border-top: 1px solid #ddd;\n}\n.table > thead > tr > th {\n  vertical-align: bottom;\n  border-bottom: 2px solid #ddd;\n}\n.table > caption + thead > tr:first-child > th,\n.table > colgroup + thead > tr:first-child > th,\n.table > thead:first-child > tr:first-child > th,\n.table > caption + thead > tr:first-child > td,\n.table > colgroup + thead > tr:first-child > td,\n.table > thead:first-child > tr:first-child > td {\n  border-top: 0;\n}\n.table > tbody + tbody {\n  border-top: 2px solid #ddd;\n}\n.table .table {\n  background-color: #fff;\n}\n.table-condensed > thead > tr > th,\n.table-condensed > tbody > tr > th,\n.table-condensed > tfoot > tr > th,\n.table-condensed > thead > tr > td,\n.table-condensed > tbody > tr > td,\n.table-condensed > tfoot > tr > td {\n  padding: 5px;\n}\n.table-bordered {\n  border: 1px solid #ddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > tbody > tr > th,\n.table-bordered > tfoot > tr > th,\n.table-bordered > thead > tr > td,\n.table-bordered > tbody > tr > td,\n.table-bordered > tfoot > tr > td {\n  border: 1px solid #ddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > thead > tr > td {\n  border-bottom-width: 2px;\n}\n.table-striped > tbody > tr:nth-of-type(odd) {\n  background-color: #f9f9f9;\n}\n.table-hover > tbody > tr:hover {\n  background-color: #f5f5f5;\n}\ntable col[class*=\"col-\"] {\n  position: static;\n  display: table-column;\n  float: none;\n}\ntable td[class*=\"col-\"],\ntable th[class*=\"col-\"] {\n  position: static;\n  display: table-cell;\n  float: none;\n}\n.table > thead > tr > td.active,\n.table > tbody > tr > td.active,\n.table > tfoot > tr > td.active,\n.table > thead > tr > th.active,\n.table > tbody > tr > th.active,\n.table > tfoot > tr > th.active,\n.table > thead > tr.active > td,\n.table > tbody > tr.active > td,\n.table > tfoot > tr.active > td,\n.table > thead > tr.active > th,\n.table > tbody > tr.active > th,\n.table > tfoot > tr.active > th {\n  background-color: #f5f5f5;\n}\n.table-hover > tbody > tr > td.active:hover,\n.table-hover > tbody > tr > th.active:hover,\n.table-hover > tbody > tr.active:hover > td,\n.table-hover > tbody > tr:hover > .active,\n.table-hover > tbody > tr.active:hover > th {\n  background-color: #e8e8e8;\n}\n.table > thead > tr > td.success,\n.table > tbody > tr > td.success,\n.table > tfoot > tr > td.success,\n.table > thead > tr > th.success,\n.table > tbody > tr > th.success,\n.table > tfoot > tr > th.success,\n.table > thead > tr.success > td,\n.table > tbody > tr.success > td,\n.table > tfoot > tr.success > td,\n.table > thead > tr.success > th,\n.table > tbody > tr.success > th,\n.table > tfoot > tr.success > th {\n  background-color: #dff0d8;\n}\n.table-hover > tbody > tr > td.success:hover,\n.table-hover > tbody > tr > th.success:hover,\n.table-hover > tbody > tr.success:hover > td,\n.table-hover > tbody > tr:hover > .success,\n.table-hover > tbody > tr.success:hover > th {\n  background-color: #d0e9c6;\n}\n.table > thead > tr > td.info,\n.table > tbody > tr > td.info,\n.table > tfoot > tr > td.info,\n.table > thead > tr > th.info,\n.table > tbody > tr > th.info,\n.table > tfoot > tr > th.info,\n.table > thead > tr.info > td,\n.table > tbody > tr.info > td,\n.table > tfoot > tr.info > td,\n.table > thead > tr.info > th,\n.table > tbody > tr.info > th,\n.table > tfoot > tr.info > th {\n  background-color: #d9edf7;\n}\n.table-hover > tbody > tr > td.info:hover,\n.table-hover > tbody > tr > th.info:hover,\n.table-hover > tbody > tr.info:hover > td,\n.table-hover > tbody > tr:hover > .info,\n.table-hover > tbody > tr.info:hover > th {\n  background-color: #c4e3f3;\n}\n.table > thead > tr > td.warning,\n.table > tbody > tr > td.warning,\n.table > tfoot > tr > td.warning,\n.table > thead > tr > th.warning,\n.table > tbody > tr > th.warning,\n.table > tfoot > tr > th.warning,\n.table > thead > tr.warning > td,\n.table > tbody > tr.warning > td,\n.table > tfoot > tr.warning > td,\n.table > thead > tr.warning > th,\n.table > tbody > tr.warning > th,\n.table > tfoot > tr.warning > th {\n  background-color: #fcf8e3;\n}\n.table-hover > tbody > tr > td.warning:hover,\n.table-hover > tbody > tr > th.warning:hover,\n.table-hover > tbody > tr.warning:hover > td,\n.table-hover > tbody > tr:hover > .warning,\n.table-hover > tbody > tr.warning:hover > th {\n  background-color: #faf2cc;\n}\n.table > thead > tr > td.danger,\n.table > tbody > tr > td.danger,\n.table > tfoot > tr > td.danger,\n.table > thead > tr > th.danger,\n.table > tbody > tr > th.danger,\n.table > tfoot > tr > th.danger,\n.table > thead > tr.danger > td,\n.table > tbody > tr.danger > td,\n.table > tfoot > tr.danger > td,\n.table > thead > tr.danger > th,\n.table > tbody > tr.danger > th,\n.table > tfoot > tr.danger > th {\n  background-color: #f2dede;\n}\n.table-hover > tbody > tr > td.danger:hover,\n.table-hover > tbody > tr > th.danger:hover,\n.table-hover > tbody > tr.danger:hover > td,\n.table-hover > tbody > tr:hover > .danger,\n.table-hover > tbody > tr.danger:hover > th {\n  background-color: #ebcccc;\n}\n.table-responsive {\n  min-height: .01%;\n  overflow-x: auto;\n}\n@media screen and (max-width: 767px) {\n  .table-responsive {\n    width: 100%;\n    margin-bottom: 15px;\n    overflow-y: hidden;\n    -ms-overflow-style: -ms-autohiding-scrollbar;\n    border: 1px solid #ddd;\n  }\n  .table-responsive > .table {\n    margin-bottom: 0;\n  }\n  .table-responsive > .table > thead > tr > th,\n  .table-responsive > .table > tbody > tr > th,\n  .table-responsive > .table > tfoot > tr > th,\n  .table-responsive > .table > thead > tr > td,\n  .table-responsive > .table > tbody > tr > td,\n  .table-responsive > .table > tfoot > tr > td {\n    white-space: nowrap;\n  }\n  .table-responsive > .table-bordered {\n    border: 0;\n  }\n  .table-responsive > .table-bordered > thead > tr > th:first-child,\n  .table-responsive > .table-bordered > tbody > tr > th:first-child,\n  .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n  .table-responsive > .table-bordered > thead > tr > td:first-child,\n  .table-responsive > .table-bordered > tbody > tr > td:first-child,\n  .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n    border-left: 0;\n  }\n  .table-responsive > .table-bordered > thead > tr > th:last-child,\n  .table-responsive > .table-bordered > tbody > tr > th:last-child,\n  .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n  .table-responsive > .table-bordered > thead > tr > td:last-child,\n  .table-responsive > .table-bordered > tbody > tr > td:last-child,\n  .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n    border-right: 0;\n  }\n  .table-responsive > .table-bordered > tbody > tr:last-child > th,\n  .table-responsive > .table-bordered > tfoot > tr:last-child > th,\n  .table-responsive > .table-bordered > tbody > tr:last-child > td,\n  .table-responsive > .table-bordered > tfoot > tr:last-child > td {\n    border-bottom: 0;\n  }\n}\nfieldset {\n  min-width: 0;\n  padding: 0;\n  margin: 0;\n  border: 0;\n}\nlegend {\n  display: block;\n  width: 100%;\n  padding: 0;\n  margin-bottom: 20px;\n  font-size: 21px;\n  line-height: inherit;\n  color: #333;\n  border: 0;\n  border-bottom: 1px solid #e5e5e5;\n}\nlabel {\n  display: inline-block;\n  max-width: 100%;\n  margin-bottom: 5px;\n  font-weight: bold;\n}\ninput[type=\"search\"] {\n  -webkit-box-sizing: border-box;\n     -moz-box-sizing: border-box;\n          box-sizing: border-box;\n}\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n  margin: 4px 0 0;\n  margin-top: 1px \\9;\n  line-height: normal;\n}\ninput[type=\"file\"] {\n  display: block;\n}\ninput[type=\"range\"] {\n  display: block;\n  width: 100%;\n}\nselect[multiple],\nselect[size] {\n  height: auto;\n}\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n  outline: thin dotted;\n  outline: 5px auto -webkit-focus-ring-color;\n  outline-offset: -2px;\n}\noutput {\n  display: block;\n  padding-top: 7px;\n  font-size: 14px;\n  line-height: 1.42857143;\n  color: #555;\n}\n.form-control {\n  display: block;\n  width: 100%;\n  height: 34px;\n  padding: 6px 12px;\n  font-size: 14px;\n  line-height: 1.42857143;\n  color: #555;\n  background-color: #fff;\n  background-image: none;\n  border: 1px solid #ccc;\n  border-radius: 4px;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n  -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;\n       -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n          transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n}\n.form-control:focus {\n  border-color: #66afe9;\n  outline: 0;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);\n          box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);\n}\n.form-control::-moz-placeholder {\n  color: #999;\n  opacity: 1;\n}\n.form-control:-ms-input-placeholder {\n  color: #999;\n}\n.form-control::-webkit-input-placeholder {\n  color: #999;\n}\n.form-control[disabled],\n.form-control[readonly],\nfieldset[disabled] .form-control {\n  cursor: not-allowed;\n  background-color: #eee;\n  opacity: 1;\n}\ntextarea.form-control {\n  height: auto;\n}\ninput[type=\"search\"] {\n  -webkit-appearance: none;\n}\n@media screen and (-webkit-min-device-pixel-ratio: 0) {\n  input[type=\"date\"],\n  input[type=\"time\"],\n  input[type=\"datetime-local\"],\n  input[type=\"month\"] {\n    line-height: 34px;\n  }\n  input[type=\"date\"].input-sm,\n  input[type=\"time\"].input-sm,\n  input[type=\"datetime-local\"].input-sm,\n  input[type=\"month\"].input-sm,\n  .input-group-sm input[type=\"date\"],\n  .input-group-sm input[type=\"time\"],\n  .input-group-sm input[type=\"datetime-local\"],\n  .input-group-sm input[type=\"month\"] {\n    line-height: 30px;\n  }\n  input[type=\"date\"].input-lg,\n  input[type=\"time\"].input-lg,\n  input[type=\"datetime-local\"].input-lg,\n  input[type=\"month\"].input-lg,\n  .input-group-lg input[type=\"date\"],\n  .input-group-lg input[type=\"time\"],\n  .input-group-lg input[type=\"datetime-local\"],\n  .input-group-lg input[type=\"month\"] {\n    line-height: 46px;\n  }\n}\n.form-group {\n  margin-bottom: 15px;\n}\n.radio,\n.checkbox {\n  position: relative;\n  display: block;\n  margin-top: 10px;\n  margin-bottom: 10px;\n}\n.radio label,\n.checkbox label {\n  min-height: 20px;\n  padding-left: 20px;\n  margin-bottom: 0;\n  font-weight: normal;\n  cursor: pointer;\n}\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n  position: absolute;\n  margin-top: 4px \\9;\n  margin-left: -20px;\n}\n.radio + .radio,\n.checkbox + .checkbox {\n  margin-top: -5px;\n}\n.radio-inline,\n.checkbox-inline {\n  display: inline-block;\n  padding-left: 20px;\n  margin-bottom: 0;\n  font-weight: normal;\n  vertical-align: middle;\n  cursor: pointer;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n  margin-top: 0;\n  margin-left: 10px;\n}\ninput[type=\"radio\"][disabled],\ninput[type=\"checkbox\"][disabled],\ninput[type=\"radio\"].disabled,\ninput[type=\"checkbox\"].disabled,\nfieldset[disabled] input[type=\"radio\"],\nfieldset[disabled] input[type=\"checkbox\"] {\n  cursor: not-allowed;\n}\n.radio-inline.disabled,\n.checkbox-inline.disabled,\nfieldset[disabled] .radio-inline,\nfieldset[disabled] .checkbox-inline {\n  cursor: not-allowed;\n}\n.radio.disabled label,\n.checkbox.disabled label,\nfieldset[disabled] .radio label,\nfieldset[disabled] .checkbox label {\n  cursor: not-allowed;\n}\n.form-control-static {\n  padding-top: 7px;\n  padding-bottom: 7px;\n  margin-bottom: 0;\n}\n.form-control-static.input-lg,\n.form-control-static.input-sm {\n  padding-right: 0;\n  padding-left: 0;\n}\n.input-sm {\n  height: 30px;\n  padding: 5px 10px;\n  font-size: 12px;\n  line-height: 1.5;\n  border-radius: 3px;\n}\nselect.input-sm {\n  height: 30px;\n  line-height: 30px;\n}\ntextarea.input-sm,\nselect[multiple].input-sm {\n  height: auto;\n}\n.form-group-sm .form-control {\n  height: 30px;\n  padding: 5px 10px;\n  font-size: 12px;\n  line-height: 1.5;\n  border-radius: 3px;\n}\nselect.form-group-sm .form-control {\n  height: 30px;\n  line-height: 30px;\n}\ntextarea.form-group-sm .form-control,\nselect[multiple].form-group-sm .form-control {\n  height: auto;\n}\n.form-group-sm .form-control-static {\n  height: 30px;\n  padding: 5px 10px;\n  font-size: 12px;\n  line-height: 1.5;\n}\n.input-lg {\n  height: 46px;\n  padding: 10px 16px;\n  font-size: 18px;\n  line-height: 1.3333333;\n  border-radius: 6px;\n}\nselect.input-lg {\n  height: 46px;\n  line-height: 46px;\n}\ntextarea.input-lg,\nselect[multiple].input-lg {\n  height: auto;\n}\n.form-group-lg .form-control {\n  height: 46px;\n  padding: 10px 16px;\n  font-size: 18px;\n  line-height: 1.3333333;\n  border-radius: 6px;\n}\nselect.form-group-lg .form-control {\n  height: 46px;\n  line-height: 46px;\n}\ntextarea.form-group-lg .form-control,\nselect[multiple].form-group-lg .form-control {\n  height: auto;\n}\n.form-group-lg .form-control-static {\n  height: 46px;\n  padding: 10px 16px;\n  font-size: 18px;\n  line-height: 1.3333333;\n}\n.has-feedback {\n  position: relative;\n}\n.has-feedback .form-control {\n  padding-right: 42.5px;\n}\n.form-control-feedback {\n  position: absolute;\n  top: 0;\n  right: 0;\n  z-index: 2;\n  display: block;\n  width: 34px;\n  height: 34px;\n  line-height: 34px;\n  text-align: center;\n  pointer-events: none;\n}\n.input-lg + .form-control-feedback {\n  width: 46px;\n  height: 46px;\n  line-height: 46px;\n}\n.input-sm + .form-control-feedback {\n  width: 30px;\n  height: 30px;\n  line-height: 30px;\n}\n.has-success .help-block,\n.has-success .control-label,\n.has-success .radio,\n.has-success .checkbox,\n.has-success .radio-inline,\n.has-success .checkbox-inline,\n.has-success.radio label,\n.has-success.checkbox label,\n.has-success.radio-inline label,\n.has-success.checkbox-inline label {\n  color: #3c763d;\n}\n.has-success .form-control {\n  border-color: #3c763d;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n}\n.has-success .form-control:focus {\n  border-color: #2b542c;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168;\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168;\n}\n.has-success .input-group-addon {\n  color: #3c763d;\n  background-color: #dff0d8;\n  border-color: #3c763d;\n}\n.has-success .form-control-feedback {\n  color: #3c763d;\n}\n.has-warning .help-block,\n.has-warning .control-label,\n.has-warning .radio,\n.has-warning .checkbox,\n.has-warning .radio-inline,\n.has-warning .checkbox-inline,\n.has-warning.radio label,\n.has-warning.checkbox label,\n.has-warning.radio-inline label,\n.has-warning.checkbox-inline label {\n  color: #8a6d3b;\n}\n.has-warning .form-control {\n  border-color: #8a6d3b;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n}\n.has-warning .form-control:focus {\n  border-color: #66512c;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b;\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b;\n}\n.has-warning .input-group-addon {\n  color: #8a6d3b;\n  background-color: #fcf8e3;\n  border-color: #8a6d3b;\n}\n.has-warning .form-control-feedback {\n  color: #8a6d3b;\n}\n.has-error .help-block,\n.has-error .control-label,\n.has-error .radio,\n.has-error .checkbox,\n.has-error .radio-inline,\n.has-error .checkbox-inline,\n.has-error.radio label,\n.has-error.checkbox label,\n.has-error.radio-inline label,\n.has-error.checkbox-inline label {\n  color: #a94442;\n}\n.has-error .form-control {\n  border-color: #a94442;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n}\n.has-error .form-control:focus {\n  border-color: #843534;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483;\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483;\n}\n.has-error .input-group-addon {\n  color: #a94442;\n  background-color: #f2dede;\n  border-color: #a94442;\n}\n.has-error .form-control-feedback {\n  color: #a94442;\n}\n.has-feedback label ~ .form-control-feedback {\n  top: 25px;\n}\n.has-feedback label.sr-only ~ .form-control-feedback {\n  top: 0;\n}\n.help-block {\n  display: block;\n  margin-top: 5px;\n  margin-bottom: 10px;\n  color: #737373;\n}\n@media (min-width: 768px) {\n  .form-inline .form-group {\n    display: inline-block;\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .form-inline .form-control {\n    display: inline-block;\n    width: auto;\n    vertical-align: middle;\n  }\n  .form-inline .form-control-static {\n    display: inline-block;\n  }\n  .form-inline .input-group {\n    display: inline-table;\n    vertical-align: middle;\n  }\n  .form-inline .input-group .input-group-addon,\n  .form-inline .input-group .input-group-btn,\n  .form-inline .input-group .form-control {\n    width: auto;\n  }\n  .form-inline .input-group > .form-control {\n    width: 100%;\n  }\n  .form-inline .control-label {\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .form-inline .radio,\n  .form-inline .checkbox {\n    display: inline-block;\n    margin-top: 0;\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .form-inline .radio label,\n  .form-inline .checkbox label {\n    padding-left: 0;\n  }\n  .form-inline .radio input[type=\"radio\"],\n  .form-inline .checkbox input[type=\"checkbox\"] {\n    position: relative;\n    margin-left: 0;\n  }\n  .form-inline .has-feedback .form-control-feedback {\n    top: 0;\n  }\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox,\n.form-horizontal .radio-inline,\n.form-horizontal .checkbox-inline {\n  padding-top: 7px;\n  margin-top: 0;\n  margin-bottom: 0;\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox {\n  min-height: 27px;\n}\n.form-horizontal .form-group {\n  margin-right: -15px;\n  margin-left: -15px;\n}\n@media (min-width: 768px) {\n  .form-horizontal .control-label {\n    padding-top: 7px;\n    margin-bottom: 0;\n    text-align: right;\n  }\n}\n.form-horizontal .has-feedback .form-control-feedback {\n  right: 15px;\n}\n@media (min-width: 768px) {\n  .form-horizontal .form-group-lg .control-label {\n    padding-top: 14.333333px;\n  }\n}\n@media (min-width: 768px) {\n  .form-horizontal .form-group-sm .control-label {\n    padding-top: 6px;\n  }\n}\n.btn {\n  display: inline-block;\n  padding: 6px 12px;\n  margin-bottom: 0;\n  font-size: 14px;\n  font-weight: normal;\n  line-height: 1.42857143;\n  text-align: center;\n  white-space: nowrap;\n  vertical-align: middle;\n  -ms-touch-action: manipulation;\n      touch-action: manipulation;\n  cursor: pointer;\n  -webkit-user-select: none;\n     -moz-user-select: none;\n      -ms-user-select: none;\n          user-select: none;\n  background-image: none;\n  border: 1px solid transparent;\n  border-radius: 4px;\n}\n.btn:focus,\n.btn:active:focus,\n.btn.active:focus,\n.btn.focus,\n.btn:active.focus,\n.btn.active.focus {\n  outline: thin dotted;\n  outline: 5px auto -webkit-focus-ring-color;\n  outline-offset: -2px;\n}\n.btn:hover,\n.btn:focus,\n.btn.focus {\n  color: #333;\n  text-decoration: none;\n}\n.btn:active,\n.btn.active {\n  background-image: none;\n  outline: 0;\n  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n          box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n}\n.btn.disabled,\n.btn[disabled],\nfieldset[disabled] .btn {\n  pointer-events: none;\n  cursor: not-allowed;\n  filter: alpha(opacity=65);\n  -webkit-box-shadow: none;\n          box-shadow: none;\n  opacity: .65;\n}\n.btn-default {\n  color: #333;\n  background-color: #fff;\n  border-color: #ccc;\n}\n.btn-default:hover,\n.btn-default:focus,\n.btn-default.focus,\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n  color: #333;\n  background-color: #e6e6e6;\n  border-color: #adadad;\n}\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n  background-image: none;\n}\n.btn-default.disabled,\n.btn-default[disabled],\nfieldset[disabled] .btn-default,\n.btn-default.disabled:hover,\n.btn-default[disabled]:hover,\nfieldset[disabled] .btn-default:hover,\n.btn-default.disabled:focus,\n.btn-default[disabled]:focus,\nfieldset[disabled] .btn-default:focus,\n.btn-default.disabled.focus,\n.btn-default[disabled].focus,\nfieldset[disabled] .btn-default.focus,\n.btn-default.disabled:active,\n.btn-default[disabled]:active,\nfieldset[disabled] .btn-default:active,\n.btn-default.disabled.active,\n.btn-default[disabled].active,\nfieldset[disabled] .btn-default.active {\n  background-color: #fff;\n  border-color: #ccc;\n}\n.btn-default .badge {\n  color: #fff;\n  background-color: #333;\n}\n.btn-primary {\n  color: #fff;\n  background-color: #337ab7;\n  border-color: #2e6da4;\n}\n.btn-primary:hover,\n.btn-primary:focus,\n.btn-primary.focus,\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n  color: #fff;\n  background-color: #286090;\n  border-color: #204d74;\n}\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n  background-image: none;\n}\n.btn-primary.disabled,\n.btn-primary[disabled],\nfieldset[disabled] .btn-primary,\n.btn-primary.disabled:hover,\n.btn-primary[disabled]:hover,\nfieldset[disabled] .btn-primary:hover,\n.btn-primary.disabled:focus,\n.btn-primary[disabled]:focus,\nfieldset[disabled] .btn-primary:focus,\n.btn-primary.disabled.focus,\n.btn-primary[disabled].focus,\nfieldset[disabled] .btn-primary.focus,\n.btn-primary.disabled:active,\n.btn-primary[disabled]:active,\nfieldset[disabled] .btn-primary:active,\n.btn-primary.disabled.active,\n.btn-primary[disabled].active,\nfieldset[disabled] .btn-primary.active {\n  background-color: #337ab7;\n  border-color: #2e6da4;\n}\n.btn-primary .badge {\n  color: #337ab7;\n  background-color: #fff;\n}\n.btn-success {\n  color: #fff;\n  background-color: #5cb85c;\n  border-color: #4cae4c;\n}\n.btn-success:hover,\n.btn-success:focus,\n.btn-success.focus,\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n  color: #fff;\n  background-color: #449d44;\n  border-color: #398439;\n}\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n  background-image: none;\n}\n.btn-success.disabled,\n.btn-success[disabled],\nfieldset[disabled] .btn-success,\n.btn-success.disabled:hover,\n.btn-success[disabled]:hover,\nfieldset[disabled] .btn-success:hover,\n.btn-success.disabled:focus,\n.btn-success[disabled]:focus,\nfieldset[disabled] .btn-success:focus,\n.btn-success.disabled.focus,\n.btn-success[disabled].focus,\nfieldset[disabled] .btn-success.focus,\n.btn-success.disabled:active,\n.btn-success[disabled]:active,\nfieldset[disabled] .btn-success:active,\n.btn-success.disabled.active,\n.btn-success[disabled].active,\nfieldset[disabled] .btn-success.active {\n  background-color: #5cb85c;\n  border-color: #4cae4c;\n}\n.btn-success .badge {\n  color: #5cb85c;\n  background-color: #fff;\n}\n.btn-info {\n  color: #fff;\n  background-color: #5bc0de;\n  border-color: #46b8da;\n}\n.btn-info:hover,\n.btn-info:focus,\n.btn-info.focus,\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n  color: #fff;\n  background-color: #31b0d5;\n  border-color: #269abc;\n}\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n  background-image: none;\n}\n.btn-info.disabled,\n.btn-info[disabled],\nfieldset[disabled] .btn-info,\n.btn-info.disabled:hover,\n.btn-info[disabled]:hover,\nfieldset[disabled] .btn-info:hover,\n.btn-info.disabled:focus,\n.btn-info[disabled]:focus,\nfieldset[disabled] .btn-info:focus,\n.btn-info.disabled.focus,\n.btn-info[disabled].focus,\nfieldset[disabled] .btn-info.focus,\n.btn-info.disabled:active,\n.btn-info[disabled]:active,\nfieldset[disabled] .btn-info:active,\n.btn-info.disabled.active,\n.btn-info[disabled].active,\nfieldset[disabled] .btn-info.active {\n  background-color: #5bc0de;\n  border-color: #46b8da;\n}\n.btn-info .badge {\n  color: #5bc0de;\n  background-color: #fff;\n}\n.btn-warning {\n  color: #fff;\n  background-color: #f0ad4e;\n  border-color: #eea236;\n}\n.btn-warning:hover,\n.btn-warning:focus,\n.btn-warning.focus,\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n  color: #fff;\n  background-color: #ec971f;\n  border-color: #d58512;\n}\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n  background-image: none;\n}\n.btn-warning.disabled,\n.btn-warning[disabled],\nfieldset[disabled] .btn-warning,\n.btn-warning.disabled:hover,\n.btn-warning[disabled]:hover,\nfieldset[disabled] .btn-warning:hover,\n.btn-warning.disabled:focus,\n.btn-warning[disabled]:focus,\nfieldset[disabled] .btn-warning:focus,\n.btn-warning.disabled.focus,\n.btn-warning[disabled].focus,\nfieldset[disabled] .btn-warning.focus,\n.btn-warning.disabled:active,\n.btn-warning[disabled]:active,\nfieldset[disabled] .btn-warning:active,\n.btn-warning.disabled.active,\n.btn-warning[disabled].active,\nfieldset[disabled] .btn-warning.active {\n  background-color: #f0ad4e;\n  border-color: #eea236;\n}\n.btn-warning .badge {\n  color: #f0ad4e;\n  background-color: #fff;\n}\n.btn-danger {\n  color: #fff;\n  background-color: #d9534f;\n  border-color: #d43f3a;\n}\n.btn-danger:hover,\n.btn-danger:focus,\n.btn-danger.focus,\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n  color: #fff;\n  background-color: #c9302c;\n  border-color: #ac2925;\n}\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n  background-image: none;\n}\n.btn-danger.disabled,\n.btn-danger[disabled],\nfieldset[disabled] .btn-danger,\n.btn-danger.disabled:hover,\n.btn-danger[disabled]:hover,\nfieldset[disabled] .btn-danger:hover,\n.btn-danger.disabled:focus,\n.btn-danger[disabled]:focus,\nfieldset[disabled] .btn-danger:focus,\n.btn-danger.disabled.focus,\n.btn-danger[disabled].focus,\nfieldset[disabled] .btn-danger.focus,\n.btn-danger.disabled:active,\n.btn-danger[disabled]:active,\nfieldset[disabled] .btn-danger:active,\n.btn-danger.disabled.active,\n.btn-danger[disabled].active,\nfieldset[disabled] .btn-danger.active {\n  background-color: #d9534f;\n  border-color: #d43f3a;\n}\n.btn-danger .badge {\n  color: #d9534f;\n  background-color: #fff;\n}\n.btn-link {\n  font-weight: normal;\n  color: #337ab7;\n  border-radius: 0;\n}\n.btn-link,\n.btn-link:active,\n.btn-link.active,\n.btn-link[disabled],\nfieldset[disabled] .btn-link {\n  background-color: transparent;\n  -webkit-box-shadow: none;\n          box-shadow: none;\n}\n.btn-link,\n.btn-link:hover,\n.btn-link:focus,\n.btn-link:active {\n  border-color: transparent;\n}\n.btn-link:hover,\n.btn-link:focus {\n  color: #23527c;\n  text-decoration: underline;\n  background-color: transparent;\n}\n.btn-link[disabled]:hover,\nfieldset[disabled] .btn-link:hover,\n.btn-link[disabled]:focus,\nfieldset[disabled] .btn-link:focus {\n  color: #777;\n  text-decoration: none;\n}\n.btn-lg,\n.btn-group-lg > .btn {\n  padding: 10px 16px;\n  font-size: 18px;\n  line-height: 1.3333333;\n  border-radius: 6px;\n}\n.btn-sm,\n.btn-group-sm > .btn {\n  padding: 5px 10px;\n  font-size: 12px;\n  line-height: 1.5;\n  border-radius: 3px;\n}\n.btn-xs,\n.btn-group-xs > .btn {\n  padding: 1px 5px;\n  font-size: 12px;\n  line-height: 1.5;\n  border-radius: 3px;\n}\n.btn-block {\n  display: block;\n  width: 100%;\n}\n.btn-block + .btn-block {\n  margin-top: 5px;\n}\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n  width: 100%;\n}\n.fade {\n  opacity: 0;\n  -webkit-transition: opacity .15s linear;\n       -o-transition: opacity .15s linear;\n          transition: opacity .15s linear;\n}\n.fade.in {\n  opacity: 1;\n}\n.collapse {\n  display: none;\n  visibility: hidden;\n}\n.collapse.in {\n  display: block;\n  visibility: visible;\n}\ntr.collapse.in {\n  display: table-row;\n}\ntbody.collapse.in {\n  display: table-row-group;\n}\n.collapsing {\n  position: relative;\n  height: 0;\n  overflow: hidden;\n  -webkit-transition-timing-function: ease;\n       -o-transition-timing-function: ease;\n          transition-timing-function: ease;\n  -webkit-transition-duration: .35s;\n       -o-transition-duration: .35s;\n          transition-duration: .35s;\n  -webkit-transition-property: height, visibility;\n       -o-transition-property: height, visibility;\n          transition-property: height, visibility;\n}\n.caret {\n  display: inline-block;\n  width: 0;\n  height: 0;\n  margin-left: 2px;\n  vertical-align: middle;\n  border-top: 4px solid;\n  border-right: 4px solid transparent;\n  border-left: 4px solid transparent;\n}\n.dropup,\n.dropdown {\n  position: relative;\n}\n.dropdown-toggle:focus {\n  outline: 0;\n}\n.dropdown-menu {\n  position: absolute;\n  top: 100%;\n  left: 0;\n  z-index: 1000;\n  display: none;\n  float: left;\n  min-width: 160px;\n  padding: 5px 0;\n  margin: 2px 0 0;\n  font-size: 14px;\n  text-align: left;\n  list-style: none;\n  background-color: #fff;\n  -webkit-background-clip: padding-box;\n          background-clip: padding-box;\n  border: 1px solid #ccc;\n  border: 1px solid rgba(0, 0, 0, .15);\n  border-radius: 4px;\n  -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175);\n          box-shadow: 0 6px 12px rgba(0, 0, 0, .175);\n}\n.dropdown-menu.pull-right {\n  right: 0;\n  left: auto;\n}\n.dropdown-menu .divider {\n  height: 1px;\n  margin: 9px 0;\n  overflow: hidden;\n  background-color: #e5e5e5;\n}\n.dropdown-menu > li > a {\n  display: block;\n  padding: 3px 20px;\n  clear: both;\n  font-weight: normal;\n  line-height: 1.42857143;\n  color: #333;\n  white-space: nowrap;\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n  color: #262626;\n  text-decoration: none;\n  background-color: #f5f5f5;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n  color: #fff;\n  text-decoration: none;\n  background-color: #337ab7;\n  outline: 0;\n}\n.dropdown-menu > .disabled > a,\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n  color: #777;\n}\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n  text-decoration: none;\n  cursor: not-allowed;\n  background-color: transparent;\n  background-image: none;\n  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n}\n.open > .dropdown-menu {\n  display: block;\n}\n.open > a {\n  outline: 0;\n}\n.dropdown-menu-right {\n  right: 0;\n  left: auto;\n}\n.dropdown-menu-left {\n  right: auto;\n  left: 0;\n}\n.dropdown-header {\n  display: block;\n  padding: 3px 20px;\n  font-size: 12px;\n  line-height: 1.42857143;\n  color: #777;\n  white-space: nowrap;\n}\n.dropdown-backdrop {\n  position: fixed;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  z-index: 990;\n}\n.pull-right > .dropdown-menu {\n  right: 0;\n  left: auto;\n}\n.dropup .caret,\n.navbar-fixed-bottom .dropdown .caret {\n  content: \"\";\n  border-top: 0;\n  border-bottom: 4px solid;\n}\n.dropup .dropdown-menu,\n.navbar-fixed-bottom .dropdown .dropdown-menu {\n  top: auto;\n  bottom: 100%;\n  margin-bottom: 2px;\n}\n@media (min-width: 768px) {\n  .navbar-right .dropdown-menu {\n    right: 0;\n    left: auto;\n  }\n  .navbar-right .dropdown-menu-left {\n    right: auto;\n    left: 0;\n  }\n}\n.btn-group,\n.btn-group-vertical {\n  position: relative;\n  display: inline-block;\n  vertical-align: middle;\n}\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n  position: relative;\n  float: left;\n}\n.btn-group > .btn:hover,\n.btn-group-vertical > .btn:hover,\n.btn-group > .btn:focus,\n.btn-group-vertical > .btn:focus,\n.btn-group > .btn:active,\n.btn-group-vertical > .btn:active,\n.btn-group > .btn.active,\n.btn-group-vertical > .btn.active {\n  z-index: 2;\n}\n.btn-group .btn + .btn,\n.btn-group .btn + .btn-group,\n.btn-group .btn-group + .btn,\n.btn-group .btn-group + .btn-group {\n  margin-left: -1px;\n}\n.btn-toolbar {\n  margin-left: -5px;\n}\n.btn-toolbar .btn-group,\n.btn-toolbar .input-group {\n  float: left;\n}\n.btn-toolbar > .btn,\n.btn-toolbar > .btn-group,\n.btn-toolbar > .input-group {\n  margin-left: 5px;\n}\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n  border-radius: 0;\n}\n.btn-group > .btn:first-child {\n  margin-left: 0;\n}\n.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.btn-group > .btn-group {\n  float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n  border-radius: 0;\n}\n.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n  outline: 0;\n}\n.btn-group > .btn + .dropdown-toggle {\n  padding-right: 8px;\n  padding-left: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n  padding-right: 12px;\n  padding-left: 12px;\n}\n.btn-group.open .dropdown-toggle {\n  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n          box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);\n}\n.btn-group.open .dropdown-toggle.btn-link {\n  -webkit-box-shadow: none;\n          box-shadow: none;\n}\n.btn .caret {\n  margin-left: 0;\n}\n.btn-lg .caret {\n  border-width: 5px 5px 0;\n  border-bottom-width: 0;\n}\n.dropup .btn-lg .caret {\n  border-width: 0 5px 5px;\n}\n.btn-group-vertical > .btn,\n.btn-group-vertical > .btn-group,\n.btn-group-vertical > .btn-group > .btn {\n  display: block;\n  float: none;\n  width: 100%;\n  max-width: 100%;\n}\n.btn-group-vertical > .btn-group > .btn {\n  float: none;\n}\n.btn-group-vertical > .btn + .btn,\n.btn-group-vertical > .btn + .btn-group,\n.btn-group-vertical > .btn-group + .btn,\n.btn-group-vertical > .btn-group + .btn-group {\n  margin-top: -1px;\n  margin-left: 0;\n}\n.btn-group-vertical > .btn:not(:first-child):not(:last-child) {\n  border-radius: 0;\n}\n.btn-group-vertical > .btn:first-child:not(:last-child) {\n  border-top-right-radius: 4px;\n  border-bottom-right-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn:last-child:not(:first-child) {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n  border-bottom-left-radius: 4px;\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n  border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n  border-bottom-right-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.btn-group-justified {\n  display: table;\n  width: 100%;\n  table-layout: fixed;\n  border-collapse: separate;\n}\n.btn-group-justified > .btn,\n.btn-group-justified > .btn-group {\n  display: table-cell;\n  float: none;\n  width: 1%;\n}\n.btn-group-justified > .btn-group .btn {\n  width: 100%;\n}\n.btn-group-justified > .btn-group .dropdown-menu {\n  left: auto;\n}\n[data-toggle=\"buttons\"] > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn input[type=\"checkbox\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"checkbox\"] {\n  position: absolute;\n  clip: rect(0, 0, 0, 0);\n  pointer-events: none;\n}\n.input-group {\n  position: relative;\n  display: table;\n  border-collapse: separate;\n}\n.input-group[class*=\"col-\"] {\n  float: none;\n  padding-right: 0;\n  padding-left: 0;\n}\n.input-group .form-control {\n  position: relative;\n  z-index: 2;\n  float: left;\n  width: 100%;\n  margin-bottom: 0;\n}\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n  height: 46px;\n  padding: 10px 16px;\n  font-size: 18px;\n  line-height: 1.3333333;\n  border-radius: 6px;\n}\nselect.input-group-lg > .form-control,\nselect.input-group-lg > .input-group-addon,\nselect.input-group-lg > .input-group-btn > .btn {\n  height: 46px;\n  line-height: 46px;\n}\ntextarea.input-group-lg > .form-control,\ntextarea.input-group-lg > .input-group-addon,\ntextarea.input-group-lg > .input-group-btn > .btn,\nselect[multiple].input-group-lg > .form-control,\nselect[multiple].input-group-lg > .input-group-addon,\nselect[multiple].input-group-lg > .input-group-btn > .btn {\n  height: auto;\n}\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n  height: 30px;\n  padding: 5px 10px;\n  font-size: 12px;\n  line-height: 1.5;\n  border-radius: 3px;\n}\nselect.input-group-sm > .form-control,\nselect.input-group-sm > .input-group-addon,\nselect.input-group-sm > .input-group-btn > .btn {\n  height: 30px;\n  line-height: 30px;\n}\ntextarea.input-group-sm > .form-control,\ntextarea.input-group-sm > .input-group-addon,\ntextarea.input-group-sm > .input-group-btn > .btn,\nselect[multiple].input-group-sm > .form-control,\nselect[multiple].input-group-sm > .input-group-addon,\nselect[multiple].input-group-sm > .input-group-btn > .btn {\n  height: auto;\n}\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n  display: table-cell;\n}\n.input-group-addon:not(:first-child):not(:last-child),\n.input-group-btn:not(:first-child):not(:last-child),\n.input-group .form-control:not(:first-child):not(:last-child) {\n  border-radius: 0;\n}\n.input-group-addon,\n.input-group-btn {\n  width: 1%;\n  white-space: nowrap;\n  vertical-align: middle;\n}\n.input-group-addon {\n  padding: 6px 12px;\n  font-size: 14px;\n  font-weight: normal;\n  line-height: 1;\n  color: #555;\n  text-align: center;\n  background-color: #eee;\n  border: 1px solid #ccc;\n  border-radius: 4px;\n}\n.input-group-addon.input-sm {\n  padding: 5px 10px;\n  font-size: 12px;\n  border-radius: 3px;\n}\n.input-group-addon.input-lg {\n  padding: 10px 16px;\n  font-size: 18px;\n  border-radius: 6px;\n}\n.input-group-addon input[type=\"radio\"],\n.input-group-addon input[type=\"checkbox\"] {\n  margin-top: 0;\n}\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.input-group-addon:first-child {\n  border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.input-group-addon:last-child {\n  border-left: 0;\n}\n.input-group-btn {\n  position: relative;\n  font-size: 0;\n  white-space: nowrap;\n}\n.input-group-btn > .btn {\n  position: relative;\n}\n.input-group-btn > .btn + .btn {\n  margin-left: -1px;\n}\n.input-group-btn > .btn:hover,\n.input-group-btn > .btn:focus,\n.input-group-btn > .btn:active {\n  z-index: 2;\n}\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group {\n  margin-right: -1px;\n}\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group {\n  margin-left: -1px;\n}\n.nav {\n  padding-left: 0;\n  margin-bottom: 0;\n  list-style: none;\n}\n.nav > li {\n  position: relative;\n  display: block;\n}\n.nav > li > a {\n  position: relative;\n  display: block;\n  padding: 10px 15px;\n}\n.nav > li > a:hover,\n.nav > li > a:focus {\n  text-decoration: none;\n  background-color: #eee;\n}\n.nav > li.disabled > a {\n  color: #777;\n}\n.nav > li.disabled > a:hover,\n.nav > li.disabled > a:focus {\n  color: #777;\n  text-decoration: none;\n  cursor: not-allowed;\n  background-color: transparent;\n}\n.nav .open > a,\n.nav .open > a:hover,\n.nav .open > a:focus {\n  background-color: #eee;\n  border-color: #337ab7;\n}\n.nav .nav-divider {\n  height: 1px;\n  margin: 9px 0;\n  overflow: hidden;\n  background-color: #e5e5e5;\n}\n.nav > li > a > img {\n  max-width: none;\n}\n.nav-tabs {\n  border-bottom: 1px solid #ddd;\n}\n.nav-tabs > li {\n  float: left;\n  margin-bottom: -1px;\n}\n.nav-tabs > li > a {\n  margin-right: 2px;\n  line-height: 1.42857143;\n  border: 1px solid transparent;\n  border-radius: 4px 4px 0 0;\n}\n.nav-tabs > li > a:hover {\n  border-color: #eee #eee #ddd;\n}\n.nav-tabs > li.active > a,\n.nav-tabs > li.active > a:hover,\n.nav-tabs > li.active > a:focus {\n  color: #555;\n  cursor: default;\n  background-color: #fff;\n  border: 1px solid #ddd;\n  border-bottom-color: transparent;\n}\n.nav-tabs.nav-justified {\n  width: 100%;\n  border-bottom: 0;\n}\n.nav-tabs.nav-justified > li {\n  float: none;\n}\n.nav-tabs.nav-justified > li > a {\n  margin-bottom: 5px;\n  text-align: center;\n}\n.nav-tabs.nav-justified > .dropdown .dropdown-menu {\n  top: auto;\n  left: auto;\n}\n@media (min-width: 768px) {\n  .nav-tabs.nav-justified > li {\n    display: table-cell;\n    width: 1%;\n  }\n  .nav-tabs.nav-justified > li > a {\n    margin-bottom: 0;\n  }\n}\n.nav-tabs.nav-justified > li > a {\n  margin-right: 0;\n  border-radius: 4px;\n}\n.nav-tabs.nav-justified > .active > a,\n.nav-tabs.nav-justified > .active > a:hover,\n.nav-tabs.nav-justified > .active > a:focus {\n  border: 1px solid #ddd;\n}\n@media (min-width: 768px) {\n  .nav-tabs.nav-justified > li > a {\n    border-bottom: 1px solid #ddd;\n    border-radius: 4px 4px 0 0;\n  }\n  .nav-tabs.nav-justified > .active > a,\n  .nav-tabs.nav-justified > .active > a:hover,\n  .nav-tabs.nav-justified > .active > a:focus {\n    border-bottom-color: #fff;\n  }\n}\n.nav-pills > li {\n  float: left;\n}\n.nav-pills > li > a {\n  border-radius: 4px;\n}\n.nav-pills > li + li {\n  margin-left: 2px;\n}\n.nav-pills > li.active > a,\n.nav-pills > li.active > a:hover,\n.nav-pills > li.active > a:focus {\n  color: #fff;\n  background-color: #337ab7;\n}\n.nav-stacked > li {\n  float: none;\n}\n.nav-stacked > li + li {\n  margin-top: 2px;\n  margin-left: 0;\n}\n.nav-justified {\n  width: 100%;\n}\n.nav-justified > li {\n  float: none;\n}\n.nav-justified > li > a {\n  margin-bottom: 5px;\n  text-align: center;\n}\n.nav-justified > .dropdown .dropdown-menu {\n  top: auto;\n  left: auto;\n}\n@media (min-width: 768px) {\n  .nav-justified > li {\n    display: table-cell;\n    width: 1%;\n  }\n  .nav-justified > li > a {\n    margin-bottom: 0;\n  }\n}\n.nav-tabs-justified {\n  border-bottom: 0;\n}\n.nav-tabs-justified > li > a {\n  margin-right: 0;\n  border-radius: 4px;\n}\n.nav-tabs-justified > .active > a,\n.nav-tabs-justified > .active > a:hover,\n.nav-tabs-justified > .active > a:focus {\n  border: 1px solid #ddd;\n}\n@media (min-width: 768px) {\n  .nav-tabs-justified > li > a {\n    border-bottom: 1px solid #ddd;\n    border-radius: 4px 4px 0 0;\n  }\n  .nav-tabs-justified > .active > a,\n  .nav-tabs-justified > .active > a:hover,\n  .nav-tabs-justified > .active > a:focus {\n    border-bottom-color: #fff;\n  }\n}\n.tab-content > .tab-pane {\n  display: none;\n  visibility: hidden;\n}\n.tab-content > .active {\n  display: block;\n  visibility: visible;\n}\n.nav-tabs .dropdown-menu {\n  margin-top: -1px;\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.navbar {\n  position: relative;\n  min-height: 50px;\n  margin-bottom: 20px;\n  border: 1px solid transparent;\n}\n@media (min-width: 768px) {\n  .navbar {\n    border-radius: 4px;\n  }\n}\n@media (min-width: 768px) {\n  .navbar-header {\n    float: left;\n  }\n}\n.navbar-collapse {\n  padding-right: 15px;\n  padding-left: 15px;\n  overflow-x: visible;\n  -webkit-overflow-scrolling: touch;\n  border-top: 1px solid transparent;\n  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1);\n          box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1);\n}\n.navbar-collapse.in {\n  overflow-y: auto;\n}\n@media (min-width: 768px) {\n  .navbar-collapse {\n    width: auto;\n    border-top: 0;\n    -webkit-box-shadow: none;\n            box-shadow: none;\n  }\n  .navbar-collapse.collapse {\n    display: block !important;\n    height: auto !important;\n    padding-bottom: 0;\n    overflow: visible !important;\n    visibility: visible !important;\n  }\n  .navbar-collapse.in {\n    overflow-y: visible;\n  }\n  .navbar-fixed-top .navbar-collapse,\n  .navbar-static-top .navbar-collapse,\n  .navbar-fixed-bottom .navbar-collapse {\n    padding-right: 0;\n    padding-left: 0;\n  }\n}\n.navbar-fixed-top .navbar-collapse,\n.navbar-fixed-bottom .navbar-collapse {\n  max-height: 340px;\n}\n@media (max-device-width: 480px) and (orientation: landscape) {\n  .navbar-fixed-top .navbar-collapse,\n  .navbar-fixed-bottom .navbar-collapse {\n    max-height: 200px;\n  }\n}\n.container > .navbar-header,\n.container-fluid > .navbar-header,\n.container > .navbar-collapse,\n.container-fluid > .navbar-collapse {\n  margin-right: -15px;\n  margin-left: -15px;\n}\n@media (min-width: 768px) {\n  .container > .navbar-header,\n  .container-fluid > .navbar-header,\n  .container > .navbar-collapse,\n  .container-fluid > .navbar-collapse {\n    margin-right: 0;\n    margin-left: 0;\n  }\n}\n.navbar-static-top {\n  z-index: 1000;\n  border-width: 0 0 1px;\n}\n@media (min-width: 768px) {\n  .navbar-static-top {\n    border-radius: 0;\n  }\n}\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n  position: fixed;\n  right: 0;\n  left: 0;\n  z-index: 1030;\n}\n@media (min-width: 768px) {\n  .navbar-fixed-top,\n  .navbar-fixed-bottom {\n    border-radius: 0;\n  }\n}\n.navbar-fixed-top {\n  top: 0;\n  border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n  bottom: 0;\n  margin-bottom: 0;\n  border-width: 1px 0 0;\n}\n.navbar-brand {\n  float: left;\n  height: 50px;\n  padding: 15px 15px;\n  font-size: 18px;\n  line-height: 20px;\n}\n.navbar-brand:hover,\n.navbar-brand:focus {\n  text-decoration: none;\n}\n.navbar-brand > img {\n  display: block;\n}\n@media (min-width: 768px) {\n  .navbar > .container .navbar-brand,\n  .navbar > .container-fluid .navbar-brand {\n    margin-left: -15px;\n  }\n}\n.navbar-toggle {\n  position: relative;\n  float: right;\n  padding: 9px 10px;\n  margin-top: 8px;\n  margin-right: 15px;\n  margin-bottom: 8px;\n  background-color: transparent;\n  background-image: none;\n  border: 1px solid transparent;\n  border-radius: 4px;\n}\n.navbar-toggle:focus {\n  outline: 0;\n}\n.navbar-toggle .icon-bar {\n  display: block;\n  width: 22px;\n  height: 2px;\n  border-radius: 1px;\n}\n.navbar-toggle .icon-bar + .icon-bar {\n  margin-top: 4px;\n}\n@media (min-width: 768px) {\n  .navbar-toggle {\n    display: none;\n  }\n}\n.navbar-nav {\n  margin: 7.5px -15px;\n}\n.navbar-nav > li > a {\n  padding-top: 10px;\n  padding-bottom: 10px;\n  line-height: 20px;\n}\n@media (max-width: 767px) {\n  .navbar-nav .open .dropdown-menu {\n    position: static;\n    float: none;\n    width: auto;\n    margin-top: 0;\n    background-color: transparent;\n    border: 0;\n    -webkit-box-shadow: none;\n            box-shadow: none;\n  }\n  .navbar-nav .open .dropdown-menu > li > a,\n  .navbar-nav .open .dropdown-menu .dropdown-header {\n    padding: 5px 15px 5px 25px;\n  }\n  .navbar-nav .open .dropdown-menu > li > a {\n    line-height: 20px;\n  }\n  .navbar-nav .open .dropdown-menu > li > a:hover,\n  .navbar-nav .open .dropdown-menu > li > a:focus {\n    background-image: none;\n  }\n}\n@media (min-width: 768px) {\n  .navbar-nav {\n    float: left;\n    margin: 0;\n  }\n  .navbar-nav > li {\n    float: left;\n  }\n  .navbar-nav > li > a {\n    padding-top: 15px;\n    padding-bottom: 15px;\n  }\n}\n.navbar-form {\n  padding: 10px 15px;\n  margin-top: 8px;\n  margin-right: -15px;\n  margin-bottom: 8px;\n  margin-left: -15px;\n  border-top: 1px solid transparent;\n  border-bottom: 1px solid transparent;\n  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1);\n          box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1);\n}\n@media (min-width: 768px) {\n  .navbar-form .form-group {\n    display: inline-block;\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .navbar-form .form-control {\n    display: inline-block;\n    width: auto;\n    vertical-align: middle;\n  }\n  .navbar-form .form-control-static {\n    display: inline-block;\n  }\n  .navbar-form .input-group {\n    display: inline-table;\n    vertical-align: middle;\n  }\n  .navbar-form .input-group .input-group-addon,\n  .navbar-form .input-group .input-group-btn,\n  .navbar-form .input-group .form-control {\n    width: auto;\n  }\n  .navbar-form .input-group > .form-control {\n    width: 100%;\n  }\n  .navbar-form .control-label {\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .navbar-form .radio,\n  .navbar-form .checkbox {\n    display: inline-block;\n    margin-top: 0;\n    margin-bottom: 0;\n    vertical-align: middle;\n  }\n  .navbar-form .radio label,\n  .navbar-form .checkbox label {\n    padding-left: 0;\n  }\n  .navbar-form .radio input[type=\"radio\"],\n  .navbar-form .checkbox input[type=\"checkbox\"] {\n    position: relative;\n    margin-left: 0;\n  }\n  .navbar-form .has-feedback .form-control-feedback {\n    top: 0;\n  }\n}\n@media (max-width: 767px) {\n  .navbar-form .form-group {\n    margin-bottom: 5px;\n  }\n  .navbar-form .form-group:last-child {\n    margin-bottom: 0;\n  }\n}\n@media (min-width: 768px) {\n  .navbar-form {\n    width: auto;\n    padding-top: 0;\n    padding-bottom: 0;\n    margin-right: 0;\n    margin-left: 0;\n    border: 0;\n    -webkit-box-shadow: none;\n            box-shadow: none;\n  }\n}\n.navbar-nav > li > .dropdown-menu {\n  margin-top: 0;\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n  margin-bottom: 0;\n  border-top-left-radius: 4px;\n  border-top-right-radius: 4px;\n  border-bottom-right-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.navbar-btn {\n  margin-top: 8px;\n  margin-bottom: 8px;\n}\n.navbar-btn.btn-sm {\n  margin-top: 10px;\n  margin-bottom: 10px;\n}\n.navbar-btn.btn-xs {\n  margin-top: 14px;\n  margin-bottom: 14px;\n}\n.navbar-text {\n  margin-top: 15px;\n  margin-bottom: 15px;\n}\n@media (min-width: 768px) {\n  .navbar-text {\n    float: left;\n    margin-right: 15px;\n    margin-left: 15px;\n  }\n}\n@media (min-width: 768px) {\n  .navbar-left {\n    float: left !important;\n  }\n  .navbar-right {\n    float: right !important;\n    margin-right: -15px;\n  }\n  .navbar-right ~ .navbar-right {\n    margin-right: 0;\n  }\n}\n.navbar-default {\n  background-color: #f8f8f8;\n  border-color: #e7e7e7;\n}\n.navbar-default .navbar-brand {\n  color: #777;\n}\n.navbar-default .navbar-brand:hover,\n.navbar-default .navbar-brand:focus {\n  color: #5e5e5e;\n  background-color: transparent;\n}\n.navbar-default .navbar-text {\n  color: #777;\n}\n.navbar-default .navbar-nav > li > a {\n  color: #777;\n}\n.navbar-default .navbar-nav > li > a:hover,\n.navbar-default .navbar-nav > li > a:focus {\n  color: #333;\n  background-color: transparent;\n}\n.navbar-default .navbar-nav > .active > a,\n.navbar-default .navbar-nav > .active > a:hover,\n.navbar-default .navbar-nav > .active > a:focus {\n  color: #555;\n  background-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .disabled > a,\n.navbar-default .navbar-nav > .disabled > a:hover,\n.navbar-default .navbar-nav > .disabled > a:focus {\n  color: #ccc;\n  background-color: transparent;\n}\n.navbar-default .navbar-toggle {\n  border-color: #ddd;\n}\n.navbar-default .navbar-toggle:hover,\n.navbar-default .navbar-toggle:focus {\n  background-color: #ddd;\n}\n.navbar-default .navbar-toggle .icon-bar {\n  background-color: #888;\n}\n.navbar-default .navbar-collapse,\n.navbar-default .navbar-form {\n  border-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .open > a:hover,\n.navbar-default .navbar-nav > .open > a:focus {\n  color: #555;\n  background-color: #e7e7e7;\n}\n@media (max-width: 767px) {\n  .navbar-default .navbar-nav .open .dropdown-menu > li > a {\n    color: #777;\n  }\n  .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,\n  .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {\n    color: #333;\n    background-color: transparent;\n  }\n  .navbar-default .navbar-nav .open .dropdown-menu > .active > a,\n  .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,\n  .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {\n    color: #555;\n    background-color: #e7e7e7;\n  }\n  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,\n  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n  .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n    color: #ccc;\n    background-color: transparent;\n  }\n}\n.navbar-default .navbar-link {\n  color: #777;\n}\n.navbar-default .navbar-link:hover {\n  color: #333;\n}\n.navbar-default .btn-link {\n  color: #777;\n}\n.navbar-default .btn-link:hover,\n.navbar-default .btn-link:focus {\n  color: #333;\n}\n.navbar-default .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-default .btn-link:hover,\n.navbar-default .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-default .btn-link:focus {\n  color: #ccc;\n}\n.navbar-inverse {\n  background-color: #222;\n  border-color: #080808;\n}\n.navbar-inverse .navbar-brand {\n  color: #9d9d9d;\n}\n.navbar-inverse .navbar-brand:hover,\n.navbar-inverse .navbar-brand:focus {\n  color: #fff;\n  background-color: transparent;\n}\n.navbar-inverse .navbar-text {\n  color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a {\n  color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a:hover,\n.navbar-inverse .navbar-nav > li > a:focus {\n  color: #fff;\n  background-color: transparent;\n}\n.navbar-inverse .navbar-nav > .active > a,\n.navbar-inverse .navbar-nav > .active > a:hover,\n.navbar-inverse .navbar-nav > .active > a:focus {\n  color: #fff;\n  background-color: #080808;\n}\n.navbar-inverse .navbar-nav > .disabled > a,\n.navbar-inverse .navbar-nav > .disabled > a:hover,\n.navbar-inverse .navbar-nav > .disabled > a:focus {\n  color: #444;\n  background-color: transparent;\n}\n.navbar-inverse .navbar-toggle {\n  border-color: #333;\n}\n.navbar-inverse .navbar-toggle:hover,\n.navbar-inverse .navbar-toggle:focus {\n  background-color: #333;\n}\n.navbar-inverse .navbar-toggle .icon-bar {\n  background-color: #fff;\n}\n.navbar-inverse .navbar-collapse,\n.navbar-inverse .navbar-form {\n  border-color: #101010;\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .open > a:hover,\n.navbar-inverse .navbar-nav > .open > a:focus {\n  color: #fff;\n  background-color: #080808;\n}\n@media (max-width: 767px) {\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {\n    border-color: #080808;\n  }\n  .navbar-inverse .navbar-nav .open .dropdown-menu .divider {\n    background-color: #080808;\n  }\n  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {\n    color: #9d9d9d;\n  }\n  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,\n  .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {\n    color: #fff;\n    background-color: transparent;\n  }\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {\n    color: #fff;\n    background-color: #080808;\n  }\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n  .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n    color: #444;\n    background-color: transparent;\n  }\n}\n.navbar-inverse .navbar-link {\n  color: #9d9d9d;\n}\n.navbar-inverse .navbar-link:hover {\n  color: #fff;\n}\n.navbar-inverse .btn-link {\n  color: #9d9d9d;\n}\n.navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link:focus {\n  color: #fff;\n}\n.navbar-inverse .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-inverse .btn-link:focus {\n  color: #444;\n}\n.breadcrumb {\n  padding: 8px 15px;\n  margin-bottom: 20px;\n  list-style: none;\n  background-color: #f5f5f5;\n  border-radius: 4px;\n}\n.breadcrumb > li {\n  display: inline-block;\n}\n.breadcrumb > li + li:before {\n  padding: 0 5px;\n  color: #ccc;\n  content: \"/\\00a0\";\n}\n.breadcrumb > .active {\n  color: #777;\n}\n.pagination {\n  display: inline-block;\n  padding-left: 0;\n  margin: 20px 0;\n  border-radius: 4px;\n}\n.pagination > li {\n  display: inline;\n}\n.pagination > li > a,\n.pagination > li > span {\n  position: relative;\n  float: left;\n  padding: 6px 12px;\n  margin-left: -1px;\n  line-height: 1.42857143;\n  color: #337ab7;\n  text-decoration: none;\n  background-color: #fff;\n  border: 1px solid #ddd;\n}\n.pagination > li:first-child > a,\n.pagination > li:first-child > span {\n  margin-left: 0;\n  border-top-left-radius: 4px;\n  border-bottom-left-radius: 4px;\n}\n.pagination > li:last-child > a,\n.pagination > li:last-child > span {\n  border-top-right-radius: 4px;\n  border-bottom-right-radius: 4px;\n}\n.pagination > li > a:hover,\n.pagination > li > span:hover,\n.pagination > li > a:focus,\n.pagination > li > span:focus {\n  color: #23527c;\n  background-color: #eee;\n  border-color: #ddd;\n}\n.pagination > .active > a,\n.pagination > .active > span,\n.pagination > .active > a:hover,\n.pagination > .active > span:hover,\n.pagination > .active > a:focus,\n.pagination > .active > span:focus {\n  z-index: 2;\n  color: #fff;\n  cursor: default;\n  background-color: #337ab7;\n  border-color: #337ab7;\n}\n.pagination > .disabled > span,\n.pagination > .disabled > span:hover,\n.pagination > .disabled > span:focus,\n.pagination > .disabled > a,\n.pagination > .disabled > a:hover,\n.pagination > .disabled > a:focus {\n  color: #777;\n  cursor: not-allowed;\n  background-color: #fff;\n  border-color: #ddd;\n}\n.pagination-lg > li > a,\n.pagination-lg > li > span {\n  padding: 10px 16px;\n  font-size: 18px;\n}\n.pagination-lg > li:first-child > a,\n.pagination-lg > li:first-child > span {\n  border-top-left-radius: 6px;\n  border-bottom-left-radius: 6px;\n}\n.pagination-lg > li:last-child > a,\n.pagination-lg > li:last-child > span {\n  border-top-right-radius: 6px;\n  border-bottom-right-radius: 6px;\n}\n.pagination-sm > li > a,\n.pagination-sm > li > span {\n  padding: 5px 10px;\n  font-size: 12px;\n}\n.pagination-sm > li:first-child > a,\n.pagination-sm > li:first-child > span {\n  border-top-left-radius: 3px;\n  border-bottom-left-radius: 3px;\n}\n.pagination-sm > li:last-child > a,\n.pagination-sm > li:last-child > span {\n  border-top-right-radius: 3px;\n  border-bottom-right-radius: 3px;\n}\n.pager {\n  padding-left: 0;\n  margin: 20px 0;\n  text-align: center;\n  list-style: none;\n}\n.pager li {\n  display: inline;\n}\n.pager li > a,\n.pager li > span {\n  display: inline-block;\n  padding: 5px 14px;\n  background-color: #fff;\n  border: 1px solid #ddd;\n  border-radius: 15px;\n}\n.pager li > a:hover,\n.pager li > a:focus {\n  text-decoration: none;\n  background-color: #eee;\n}\n.pager .next > a,\n.pager .next > span {\n  float: right;\n}\n.pager .previous > a,\n.pager .previous > span {\n  float: left;\n}\n.pager .disabled > a,\n.pager .disabled > a:hover,\n.pager .disabled > a:focus,\n.pager .disabled > span {\n  color: #777;\n  cursor: not-allowed;\n  background-color: #fff;\n}\n.label {\n  display: inline;\n  padding: .2em .6em .3em;\n  font-size: 75%;\n  font-weight: bold;\n  line-height: 1;\n  color: #fff;\n  text-align: center;\n  white-space: nowrap;\n  vertical-align: baseline;\n  border-radius: .25em;\n}\na.label:hover,\na.label:focus {\n  color: #fff;\n  text-decoration: none;\n  cursor: pointer;\n}\n.label:empty {\n  display: none;\n}\n.btn .label {\n  position: relative;\n  top: -1px;\n}\n.label-default {\n  background-color: #777;\n}\n.label-default[href]:hover,\n.label-default[href]:focus {\n  background-color: #5e5e5e;\n}\n.label-primary {\n  background-color: #337ab7;\n}\n.label-primary[href]:hover,\n.label-primary[href]:focus {\n  background-color: #286090;\n}\n.label-success {\n  background-color: #5cb85c;\n}\n.label-success[href]:hover,\n.label-success[href]:focus {\n  background-color: #449d44;\n}\n.label-info {\n  background-color: #5bc0de;\n}\n.label-info[href]:hover,\n.label-info[href]:focus {\n  background-color: #31b0d5;\n}\n.label-warning {\n  background-color: #f0ad4e;\n}\n.label-warning[href]:hover,\n.label-warning[href]:focus {\n  background-color: #ec971f;\n}\n.label-danger {\n  background-color: #d9534f;\n}\n.label-danger[href]:hover,\n.label-danger[href]:focus {\n  background-color: #c9302c;\n}\n.badge {\n  display: inline-block;\n  min-width: 10px;\n  padding: 3px 7px;\n  font-size: 12px;\n  font-weight: bold;\n  line-height: 1;\n  color: #fff;\n  text-align: center;\n  white-space: nowrap;\n  vertical-align: baseline;\n  background-color: #777;\n  border-radius: 10px;\n}\n.badge:empty {\n  display: none;\n}\n.btn .badge {\n  position: relative;\n  top: -1px;\n}\n.btn-xs .badge {\n  top: 0;\n  padding: 1px 5px;\n}\na.badge:hover,\na.badge:focus {\n  color: #fff;\n  text-decoration: none;\n  cursor: pointer;\n}\n.list-group-item.active > .badge,\n.nav-pills > .active > a > .badge {\n  color: #337ab7;\n  background-color: #fff;\n}\n.list-group-item > .badge {\n  float: right;\n}\n.list-group-item > .badge + .badge {\n  margin-right: 5px;\n}\n.nav-pills > li > a > .badge {\n  margin-left: 3px;\n}\n.jumbotron {\n  padding: 30px 15px;\n  margin-bottom: 30px;\n  color: inherit;\n  background-color: #eee;\n}\n.jumbotron h1,\n.jumbotron .h1 {\n  color: inherit;\n}\n.jumbotron p {\n  margin-bottom: 15px;\n  font-size: 21px;\n  font-weight: 200;\n}\n.jumbotron > hr {\n  border-top-color: #d5d5d5;\n}\n.container .jumbotron,\n.container-fluid .jumbotron {\n  border-radius: 6px;\n}\n.jumbotron .container {\n  max-width: 100%;\n}\n@media screen and (min-width: 768px) {\n  .jumbotron {\n    padding: 48px 0;\n  }\n  .container .jumbotron,\n  .container-fluid .jumbotron {\n    padding-right: 60px;\n    padding-left: 60px;\n  }\n  .jumbotron h1,\n  .jumbotron .h1 {\n    font-size: 63px;\n  }\n}\n.thumbnail {\n  display: block;\n  padding: 4px;\n  margin-bottom: 20px;\n  line-height: 1.42857143;\n  background-color: #fff;\n  border: 1px solid #ddd;\n  border-radius: 4px;\n  -webkit-transition: border .2s ease-in-out;\n       -o-transition: border .2s ease-in-out;\n          transition: border .2s ease-in-out;\n}\n.thumbnail > img,\n.thumbnail a > img {\n  margin-right: auto;\n  margin-left: auto;\n}\na.thumbnail:hover,\na.thumbnail:focus,\na.thumbnail.active {\n  border-color: #337ab7;\n}\n.thumbnail .caption {\n  padding: 9px;\n  color: #333;\n}\n.alert {\n  padding: 15px;\n  margin-bottom: 20px;\n  border: 1px solid transparent;\n  border-radius: 4px;\n}\n.alert h4 {\n  margin-top: 0;\n  color: inherit;\n}\n.alert .alert-link {\n  font-weight: bold;\n}\n.alert > p,\n.alert > ul {\n  margin-bottom: 0;\n}\n.alert > p + p {\n  margin-top: 5px;\n}\n.alert-dismissable,\n.alert-dismissible {\n  padding-right: 35px;\n}\n.alert-dismissable .close,\n.alert-dismissible .close {\n  position: relative;\n  top: -2px;\n  right: -21px;\n  color: inherit;\n}\n.alert-success {\n  color: #3c763d;\n  background-color: #dff0d8;\n  border-color: #d6e9c6;\n}\n.alert-success hr {\n  border-top-color: #c9e2b3;\n}\n.alert-success .alert-link {\n  color: #2b542c;\n}\n.alert-info {\n  color: #31708f;\n  background-color: #d9edf7;\n  border-color: #bce8f1;\n}\n.alert-info hr {\n  border-top-color: #a6e1ec;\n}\n.alert-info .alert-link {\n  color: #245269;\n}\n.alert-warning {\n  color: #8a6d3b;\n  background-color: #fcf8e3;\n  border-color: #faebcc;\n}\n.alert-warning hr {\n  border-top-color: #f7e1b5;\n}\n.alert-warning .alert-link {\n  color: #66512c;\n}\n.alert-danger {\n  color: #a94442;\n  background-color: #f2dede;\n  border-color: #ebccd1;\n}\n.alert-danger hr {\n  border-top-color: #e4b9c0;\n}\n.alert-danger .alert-link {\n  color: #843534;\n}\n@-webkit-keyframes progress-bar-stripes {\n  from {\n    background-position: 40px 0;\n  }\n  to {\n    background-position: 0 0;\n  }\n}\n@-o-keyframes progress-bar-stripes {\n  from {\n    background-position: 40px 0;\n  }\n  to {\n    background-position: 0 0;\n  }\n}\n@keyframes progress-bar-stripes {\n  from {\n    background-position: 40px 0;\n  }\n  to {\n    background-position: 0 0;\n  }\n}\n.progress {\n  height: 20px;\n  margin-bottom: 20px;\n  overflow: hidden;\n  background-color: #f5f5f5;\n  border-radius: 4px;\n  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);\n          box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);\n}\n.progress-bar {\n  float: left;\n  width: 0;\n  height: 100%;\n  font-size: 12px;\n  line-height: 20px;\n  color: #fff;\n  text-align: center;\n  background-color: #337ab7;\n  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);\n          box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);\n  -webkit-transition: width .6s ease;\n       -o-transition: width .6s ease;\n          transition: width .6s ease;\n}\n.progress-striped .progress-bar,\n.progress-bar-striped {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  -webkit-background-size: 40px 40px;\n          background-size: 40px 40px;\n}\n.progress.active .progress-bar,\n.progress-bar.active {\n  -webkit-animation: progress-bar-stripes 2s linear infinite;\n       -o-animation: progress-bar-stripes 2s linear infinite;\n          animation: progress-bar-stripes 2s linear infinite;\n}\n.progress-bar-success {\n  background-color: #5cb85c;\n}\n.progress-striped .progress-bar-success {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n}\n.progress-bar-info {\n  background-color: #5bc0de;\n}\n.progress-striped .progress-bar-info {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n}\n.progress-bar-warning {\n  background-color: #f0ad4e;\n}\n.progress-striped .progress-bar-warning {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n}\n.progress-bar-danger {\n  background-color: #d9534f;\n}\n.progress-striped .progress-bar-danger {\n  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:      -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n  background-image:         linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);\n}\n.media {\n  margin-top: 15px;\n}\n.media:first-child {\n  margin-top: 0;\n}\n.media,\n.media-body {\n  overflow: hidden;\n  zoom: 1;\n}\n.media-body {\n  width: 10000px;\n}\n.media-object {\n  display: block;\n}\n.media-right,\n.media > .pull-right {\n  padding-left: 10px;\n}\n.media-left,\n.media > .pull-left {\n  padding-right: 10px;\n}\n.media-left,\n.media-right,\n.media-body {\n  display: table-cell;\n  vertical-align: top;\n}\n.media-middle {\n  vertical-align: middle;\n}\n.media-bottom {\n  vertical-align: bottom;\n}\n.media-heading {\n  margin-top: 0;\n  margin-bottom: 5px;\n}\n.media-list {\n  padding-left: 0;\n  list-style: none;\n}\n.list-group {\n  padding-left: 0;\n  margin-bottom: 20px;\n}\n.list-group-item {\n  position: relative;\n  display: block;\n  padding: 10px 15px;\n  margin-bottom: -1px;\n  background-color: #fff;\n  border: 1px solid #ddd;\n}\n.list-group-item:first-child {\n  border-top-left-radius: 4px;\n  border-top-right-radius: 4px;\n}\n.list-group-item:last-child {\n  margin-bottom: 0;\n  border-bottom-right-radius: 4px;\n  border-bottom-left-radius: 4px;\n}\na.list-group-item {\n  color: #555;\n}\na.list-group-item .list-group-item-heading {\n  color: #333;\n}\na.list-group-item:hover,\na.list-group-item:focus {\n  color: #555;\n  text-decoration: none;\n  background-color: #f5f5f5;\n}\n.list-group-item.disabled,\n.list-group-item.disabled:hover,\n.list-group-item.disabled:focus {\n  color: #777;\n  cursor: not-allowed;\n  background-color: #eee;\n}\n.list-group-item.disabled .list-group-item-heading,\n.list-group-item.disabled:hover .list-group-item-heading,\n.list-group-item.disabled:focus .list-group-item-heading {\n  color: inherit;\n}\n.list-group-item.disabled .list-group-item-text,\n.list-group-item.disabled:hover .list-group-item-text,\n.list-group-item.disabled:focus .list-group-item-text {\n  color: #777;\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n  z-index: 2;\n  color: #fff;\n  background-color: #337ab7;\n  border-color: #337ab7;\n}\n.list-group-item.active .list-group-item-heading,\n.list-group-item.active:hover .list-group-item-heading,\n.list-group-item.active:focus .list-group-item-heading,\n.list-group-item.active .list-group-item-heading > small,\n.list-group-item.active:hover .list-group-item-heading > small,\n.list-group-item.active:focus .list-group-item-heading > small,\n.list-group-item.active .list-group-item-heading > .small,\n.list-group-item.active:hover .list-group-item-heading > .small,\n.list-group-item.active:focus .list-group-item-heading > .small {\n  color: inherit;\n}\n.list-group-item.active .list-group-item-text,\n.list-group-item.active:hover .list-group-item-text,\n.list-group-item.active:focus .list-group-item-text {\n  color: #c7ddef;\n}\n.list-group-item-success {\n  color: #3c763d;\n  background-color: #dff0d8;\n}\na.list-group-item-success {\n  color: #3c763d;\n}\na.list-group-item-success .list-group-item-heading {\n  color: inherit;\n}\na.list-group-item-success:hover,\na.list-group-item-success:focus {\n  color: #3c763d;\n  background-color: #d0e9c6;\n}\na.list-group-item-success.active,\na.list-group-item-success.active:hover,\na.list-group-item-success.active:focus {\n  color: #fff;\n  background-color: #3c763d;\n  border-color: #3c763d;\n}\n.list-group-item-info {\n  color: #31708f;\n  background-color: #d9edf7;\n}\na.list-group-item-info {\n  color: #31708f;\n}\na.list-group-item-info .list-group-item-heading {\n  color: inherit;\n}\na.list-group-item-info:hover,\na.list-group-item-info:focus {\n  color: #31708f;\n  background-color: #c4e3f3;\n}\na.list-group-item-info.active,\na.list-group-item-info.active:hover,\na.list-group-item-info.active:focus {\n  color: #fff;\n  background-color: #31708f;\n  border-color: #31708f;\n}\n.list-group-item-warning {\n  color: #8a6d3b;\n  background-color: #fcf8e3;\n}\na.list-group-item-warning {\n  color: #8a6d3b;\n}\na.list-group-item-warning .list-group-item-heading {\n  color: inherit;\n}\na.list-group-item-warning:hover,\na.list-group-item-warning:focus {\n  color: #8a6d3b;\n  background-color: #faf2cc;\n}\na.list-group-item-warning.active,\na.list-group-item-warning.active:hover,\na.list-group-item-warning.active:focus {\n  color: #fff;\n  background-color: #8a6d3b;\n  border-color: #8a6d3b;\n}\n.list-group-item-danger {\n  color: #a94442;\n  background-color: #f2dede;\n}\na.list-group-item-danger {\n  color: #a94442;\n}\na.list-group-item-danger .list-group-item-heading {\n  color: inherit;\n}\na.list-group-item-danger:hover,\na.list-group-item-danger:focus {\n  color: #a94442;\n  background-color: #ebcccc;\n}\na.list-group-item-danger.active,\na.list-group-item-danger.active:hover,\na.list-group-item-danger.active:focus {\n  color: #fff;\n  background-color: #a94442;\n  border-color: #a94442;\n}\n.list-group-item-heading {\n  margin-top: 0;\n  margin-bottom: 5px;\n}\n.list-group-item-text {\n  margin-bottom: 0;\n  line-height: 1.3;\n}\n.panel {\n  margin-bottom: 20px;\n  background-color: #fff;\n  border: 1px solid transparent;\n  border-radius: 4px;\n  -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05);\n          box-shadow: 0 1px 1px rgba(0, 0, 0, .05);\n}\n.panel-body {\n  padding: 15px;\n}\n.panel-heading {\n  padding: 10px 15px;\n  border-bottom: 1px solid transparent;\n  border-top-left-radius: 3px;\n  border-top-right-radius: 3px;\n}\n.panel-heading > .dropdown .dropdown-toggle {\n  color: inherit;\n}\n.panel-title {\n  margin-top: 0;\n  margin-bottom: 0;\n  font-size: 16px;\n  color: inherit;\n}\n.panel-title > a,\n.panel-title > small,\n.panel-title > .small,\n.panel-title > small > a,\n.panel-title > .small > a {\n  color: inherit;\n}\n.panel-footer {\n  padding: 10px 15px;\n  background-color: #f5f5f5;\n  border-top: 1px solid #ddd;\n  border-bottom-right-radius: 3px;\n  border-bottom-left-radius: 3px;\n}\n.panel > .list-group,\n.panel > .panel-collapse > .list-group {\n  margin-bottom: 0;\n}\n.panel > .list-group .list-group-item,\n.panel > .panel-collapse > .list-group .list-group-item {\n  border-width: 1px 0;\n  border-radius: 0;\n}\n.panel > .list-group:first-child .list-group-item:first-child,\n.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child {\n  border-top: 0;\n  border-top-left-radius: 3px;\n  border-top-right-radius: 3px;\n}\n.panel > .list-group:last-child .list-group-item:last-child,\n.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child {\n  border-bottom: 0;\n  border-bottom-right-radius: 3px;\n  border-bottom-left-radius: 3px;\n}\n.panel-heading + .list-group .list-group-item:first-child {\n  border-top-width: 0;\n}\n.list-group + .panel-footer {\n  border-top-width: 0;\n}\n.panel > .table,\n.panel > .table-responsive > .table,\n.panel > .panel-collapse > .table {\n  margin-bottom: 0;\n}\n.panel > .table caption,\n.panel > .table-responsive > .table caption,\n.panel > .panel-collapse > .table caption {\n  padding-right: 15px;\n  padding-left: 15px;\n}\n.panel > .table:first-child,\n.panel > .table-responsive:first-child > .table:first-child {\n  border-top-left-radius: 3px;\n  border-top-right-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child {\n  border-top-left-radius: 3px;\n  border-top-right-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child {\n  border-top-left-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child {\n  border-top-right-radius: 3px;\n}\n.panel > .table:last-child,\n.panel > .table-responsive:last-child > .table:last-child {\n  border-bottom-right-radius: 3px;\n  border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child {\n  border-bottom-right-radius: 3px;\n  border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child {\n  border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child {\n  border-bottom-right-radius: 3px;\n}\n.panel > .panel-body + .table,\n.panel > .panel-body + .table-responsive,\n.panel > .table + .panel-body,\n.panel > .table-responsive + .panel-body {\n  border-top: 1px solid #ddd;\n}\n.panel > .table > tbody:first-child > tr:first-child th,\n.panel > .table > tbody:first-child > tr:first-child td {\n  border-top: 0;\n}\n.panel > .table-bordered,\n.panel > .table-responsive > .table-bordered {\n  border: 0;\n}\n.panel > .table-bordered > thead > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:first-child,\n.panel > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-bordered > thead > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:first-child,\n.panel > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-bordered > tfoot > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n  border-left: 0;\n}\n.panel > .table-bordered > thead > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:last-child,\n.panel > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-bordered > thead > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:last-child,\n.panel > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-bordered > tfoot > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n  border-right: 0;\n}\n.panel > .table-bordered > thead > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > td,\n.panel > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-bordered > thead > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > th,\n.panel > .table-bordered > tbody > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th {\n  border-bottom: 0;\n}\n.panel > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-bordered > tfoot > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th {\n  border-bottom: 0;\n}\n.panel > .table-responsive {\n  margin-bottom: 0;\n  border: 0;\n}\n.panel-group {\n  margin-bottom: 20px;\n}\n.panel-group .panel {\n  margin-bottom: 0;\n  border-radius: 4px;\n}\n.panel-group .panel + .panel {\n  margin-top: 5px;\n}\n.panel-group .panel-heading {\n  border-bottom: 0;\n}\n.panel-group .panel-heading + .panel-collapse > .panel-body,\n.panel-group .panel-heading + .panel-collapse > .list-group {\n  border-top: 1px solid #ddd;\n}\n.panel-group .panel-footer {\n  border-top: 0;\n}\n.panel-group .panel-footer + .panel-collapse .panel-body {\n  border-bottom: 1px solid #ddd;\n}\n.panel-default {\n  border-color: #ddd;\n}\n.panel-default > .panel-heading {\n  color: #333;\n  background-color: #f5f5f5;\n  border-color: #ddd;\n}\n.panel-default > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #ddd;\n}\n.panel-default > .panel-heading .badge {\n  color: #f5f5f5;\n  background-color: #333;\n}\n.panel-default > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #ddd;\n}\n.panel-primary {\n  border-color: #337ab7;\n}\n.panel-primary > .panel-heading {\n  color: #fff;\n  background-color: #337ab7;\n  border-color: #337ab7;\n}\n.panel-primary > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #337ab7;\n}\n.panel-primary > .panel-heading .badge {\n  color: #337ab7;\n  background-color: #fff;\n}\n.panel-primary > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #337ab7;\n}\n.panel-success {\n  border-color: #d6e9c6;\n}\n.panel-success > .panel-heading {\n  color: #3c763d;\n  background-color: #dff0d8;\n  border-color: #d6e9c6;\n}\n.panel-success > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #d6e9c6;\n}\n.panel-success > .panel-heading .badge {\n  color: #dff0d8;\n  background-color: #3c763d;\n}\n.panel-success > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #d6e9c6;\n}\n.panel-info {\n  border-color: #bce8f1;\n}\n.panel-info > .panel-heading {\n  color: #31708f;\n  background-color: #d9edf7;\n  border-color: #bce8f1;\n}\n.panel-info > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #bce8f1;\n}\n.panel-info > .panel-heading .badge {\n  color: #d9edf7;\n  background-color: #31708f;\n}\n.panel-info > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #bce8f1;\n}\n.panel-warning {\n  border-color: #faebcc;\n}\n.panel-warning > .panel-heading {\n  color: #8a6d3b;\n  background-color: #fcf8e3;\n  border-color: #faebcc;\n}\n.panel-warning > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #faebcc;\n}\n.panel-warning > .panel-heading .badge {\n  color: #fcf8e3;\n  background-color: #8a6d3b;\n}\n.panel-warning > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #faebcc;\n}\n.panel-danger {\n  border-color: #ebccd1;\n}\n.panel-danger > .panel-heading {\n  color: #a94442;\n  background-color: #f2dede;\n  border-color: #ebccd1;\n}\n.panel-danger > .panel-heading + .panel-collapse > .panel-body {\n  border-top-color: #ebccd1;\n}\n.panel-danger > .panel-heading .badge {\n  color: #f2dede;\n  background-color: #a94442;\n}\n.panel-danger > .panel-footer + .panel-collapse > .panel-body {\n  border-bottom-color: #ebccd1;\n}\n.embed-responsive {\n  position: relative;\n  display: block;\n  height: 0;\n  padding: 0;\n  overflow: hidden;\n}\n.embed-responsive .embed-responsive-item,\n.embed-responsive iframe,\n.embed-responsive embed,\n.embed-responsive object,\n.embed-responsive video {\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  border: 0;\n}\n.embed-responsive.embed-responsive-16by9 {\n  padding-bottom: 56.25%;\n}\n.embed-responsive.embed-responsive-4by3 {\n  padding-bottom: 75%;\n}\n.well {\n  min-height: 20px;\n  padding: 19px;\n  margin-bottom: 20px;\n  background-color: #f5f5f5;\n  border: 1px solid #e3e3e3;\n  border-radius: 4px;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);\n          box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);\n}\n.well blockquote {\n  border-color: #ddd;\n  border-color: rgba(0, 0, 0, .15);\n}\n.well-lg {\n  padding: 24px;\n  border-radius: 6px;\n}\n.well-sm {\n  padding: 9px;\n  border-radius: 3px;\n}\n.close {\n  float: right;\n  font-size: 21px;\n  font-weight: bold;\n  line-height: 1;\n  color: #000;\n  text-shadow: 0 1px 0 #fff;\n  filter: alpha(opacity=20);\n  opacity: .2;\n}\n.close:hover,\n.close:focus {\n  color: #000;\n  text-decoration: none;\n  cursor: pointer;\n  filter: alpha(opacity=50);\n  opacity: .5;\n}\nbutton.close {\n  -webkit-appearance: none;\n  padding: 0;\n  cursor: pointer;\n  background: transparent;\n  border: 0;\n}\n.modal-open {\n  overflow: hidden;\n}\n.modal {\n  position: fixed;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  z-index: 1040;\n  display: none;\n  overflow: hidden;\n  -webkit-overflow-scrolling: touch;\n  outline: 0;\n}\n.modal.fade .modal-dialog {\n  -webkit-transition: -webkit-transform .3s ease-out;\n       -o-transition:      -o-transform .3s ease-out;\n          transition:         transform .3s ease-out;\n  -webkit-transform: translate(0, -25%);\n      -ms-transform: translate(0, -25%);\n       -o-transform: translate(0, -25%);\n          transform: translate(0, -25%);\n}\n.modal.in .modal-dialog {\n  -webkit-transform: translate(0, 0);\n      -ms-transform: translate(0, 0);\n       -o-transform: translate(0, 0);\n          transform: translate(0, 0);\n}\n.modal-open .modal {\n  overflow-x: hidden;\n  overflow-y: auto;\n}\n.modal-dialog {\n  position: relative;\n  width: auto;\n  margin: 10px;\n}\n.modal-content {\n  position: relative;\n  background-color: #fff;\n  -webkit-background-clip: padding-box;\n          background-clip: padding-box;\n  border: 1px solid #999;\n  border: 1px solid rgba(0, 0, 0, .2);\n  border-radius: 6px;\n  outline: 0;\n  -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5);\n          box-shadow: 0 3px 9px rgba(0, 0, 0, .5);\n}\n.modal-backdrop {\n  position: absolute;\n  top: 0;\n  right: 0;\n  left: 0;\n  background-color: #000;\n}\n.modal-backdrop.fade {\n  filter: alpha(opacity=0);\n  opacity: 0;\n}\n.modal-backdrop.in {\n  filter: alpha(opacity=50);\n  opacity: .5;\n}\n.modal-header {\n  min-height: 16.42857143px;\n  padding: 15px;\n  border-bottom: 1px solid #e5e5e5;\n}\n.modal-header .close {\n  margin-top: -2px;\n}\n.modal-title {\n  margin: 0;\n  line-height: 1.42857143;\n}\n.modal-body {\n  position: relative;\n  padding: 15px;\n}\n.modal-footer {\n  padding: 15px;\n  text-align: right;\n  border-top: 1px solid #e5e5e5;\n}\n.modal-footer .btn + .btn {\n  margin-bottom: 0;\n  margin-left: 5px;\n}\n.modal-footer .btn-group .btn + .btn {\n  margin-left: -1px;\n}\n.modal-footer .btn-block + .btn-block {\n  margin-left: 0;\n}\n.modal-scrollbar-measure {\n  position: absolute;\n  top: -9999px;\n  width: 50px;\n  height: 50px;\n  overflow: scroll;\n}\n@media (min-width: 768px) {\n  .modal-dialog {\n    width: 600px;\n    margin: 30px auto;\n  }\n  .modal-content {\n    -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5);\n            box-shadow: 0 5px 15px rgba(0, 0, 0, .5);\n  }\n  .modal-sm {\n    width: 300px;\n  }\n}\n@media (min-width: 992px) {\n  .modal-lg {\n    width: 900px;\n  }\n}\n.tooltip {\n  position: absolute;\n  z-index: 1070;\n  display: block;\n  font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  font-size: 12px;\n  font-weight: normal;\n  line-height: 1.4;\n  visibility: visible;\n  filter: alpha(opacity=0);\n  opacity: 0;\n}\n.tooltip.in {\n  filter: alpha(opacity=90);\n  opacity: .9;\n}\n.tooltip.top {\n  padding: 5px 0;\n  margin-top: -3px;\n}\n.tooltip.right {\n  padding: 0 5px;\n  margin-left: 3px;\n}\n.tooltip.bottom {\n  padding: 5px 0;\n  margin-top: 3px;\n}\n.tooltip.left {\n  padding: 0 5px;\n  margin-left: -3px;\n}\n.tooltip-inner {\n  max-width: 200px;\n  padding: 3px 8px;\n  color: #fff;\n  text-align: center;\n  text-decoration: none;\n  background-color: #000;\n  border-radius: 4px;\n}\n.tooltip-arrow {\n  position: absolute;\n  width: 0;\n  height: 0;\n  border-color: transparent;\n  border-style: solid;\n}\n.tooltip.top .tooltip-arrow {\n  bottom: 0;\n  left: 50%;\n  margin-left: -5px;\n  border-width: 5px 5px 0;\n  border-top-color: #000;\n}\n.tooltip.top-left .tooltip-arrow {\n  right: 5px;\n  bottom: 0;\n  margin-bottom: -5px;\n  border-width: 5px 5px 0;\n  border-top-color: #000;\n}\n.tooltip.top-right .tooltip-arrow {\n  bottom: 0;\n  left: 5px;\n  margin-bottom: -5px;\n  border-width: 5px 5px 0;\n  border-top-color: #000;\n}\n.tooltip.right .tooltip-arrow {\n  top: 50%;\n  left: 0;\n  margin-top: -5px;\n  border-width: 5px 5px 5px 0;\n  border-right-color: #000;\n}\n.tooltip.left .tooltip-arrow {\n  top: 50%;\n  right: 0;\n  margin-top: -5px;\n  border-width: 5px 0 5px 5px;\n  border-left-color: #000;\n}\n.tooltip.bottom .tooltip-arrow {\n  top: 0;\n  left: 50%;\n  margin-left: -5px;\n  border-width: 0 5px 5px;\n  border-bottom-color: #000;\n}\n.tooltip.bottom-left .tooltip-arrow {\n  top: 0;\n  right: 5px;\n  margin-top: -5px;\n  border-width: 0 5px 5px;\n  border-bottom-color: #000;\n}\n.tooltip.bottom-right .tooltip-arrow {\n  top: 0;\n  left: 5px;\n  margin-top: -5px;\n  border-width: 0 5px 5px;\n  border-bottom-color: #000;\n}\n.popover {\n  position: absolute;\n  top: 0;\n  left: 0;\n  z-index: 1060;\n  display: none;\n  max-width: 276px;\n  padding: 1px;\n  font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  font-size: 14px;\n  font-weight: normal;\n  line-height: 1.42857143;\n  text-align: left;\n  white-space: normal;\n  background-color: #fff;\n  -webkit-background-clip: padding-box;\n          background-clip: padding-box;\n  border: 1px solid #ccc;\n  border: 1px solid rgba(0, 0, 0, .2);\n  border-radius: 6px;\n  -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2);\n          box-shadow: 0 5px 10px rgba(0, 0, 0, .2);\n}\n.popover.top {\n  margin-top: -10px;\n}\n.popover.right {\n  margin-left: 10px;\n}\n.popover.bottom {\n  margin-top: 10px;\n}\n.popover.left {\n  margin-left: -10px;\n}\n.popover-title {\n  padding: 8px 14px;\n  margin: 0;\n  font-size: 14px;\n  background-color: #f7f7f7;\n  border-bottom: 1px solid #ebebeb;\n  border-radius: 5px 5px 0 0;\n}\n.popover-content {\n  padding: 9px 14px;\n}\n.popover > .arrow,\n.popover > .arrow:after {\n  position: absolute;\n  display: block;\n  width: 0;\n  height: 0;\n  border-color: transparent;\n  border-style: solid;\n}\n.popover > .arrow {\n  border-width: 11px;\n}\n.popover > .arrow:after {\n  content: \"\";\n  border-width: 10px;\n}\n.popover.top > .arrow {\n  bottom: -11px;\n  left: 50%;\n  margin-left: -11px;\n  border-top-color: #999;\n  border-top-color: rgba(0, 0, 0, .25);\n  border-bottom-width: 0;\n}\n.popover.top > .arrow:after {\n  bottom: 1px;\n  margin-left: -10px;\n  content: \" \";\n  border-top-color: #fff;\n  border-bottom-width: 0;\n}\n.popover.right > .arrow {\n  top: 50%;\n  left: -11px;\n  margin-top: -11px;\n  border-right-color: #999;\n  border-right-color: rgba(0, 0, 0, .25);\n  border-left-width: 0;\n}\n.popover.right > .arrow:after {\n  bottom: -10px;\n  left: 1px;\n  content: \" \";\n  border-right-color: #fff;\n  border-left-width: 0;\n}\n.popover.bottom > .arrow {\n  top: -11px;\n  left: 50%;\n  margin-left: -11px;\n  border-top-width: 0;\n  border-bottom-color: #999;\n  border-bottom-color: rgba(0, 0, 0, .25);\n}\n.popover.bottom > .arrow:after {\n  top: 1px;\n  margin-left: -10px;\n  content: \" \";\n  border-top-width: 0;\n  border-bottom-color: #fff;\n}\n.popover.left > .arrow {\n  top: 50%;\n  right: -11px;\n  margin-top: -11px;\n  border-right-width: 0;\n  border-left-color: #999;\n  border-left-color: rgba(0, 0, 0, .25);\n}\n.popover.left > .arrow:after {\n  right: 1px;\n  bottom: -10px;\n  content: \" \";\n  border-right-width: 0;\n  border-left-color: #fff;\n}\n.carousel {\n  position: relative;\n}\n.carousel-inner {\n  position: relative;\n  width: 100%;\n  overflow: hidden;\n}\n.carousel-inner > .item {\n  position: relative;\n  display: none;\n  -webkit-transition: .6s ease-in-out left;\n       -o-transition: .6s ease-in-out left;\n          transition: .6s ease-in-out left;\n}\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n  line-height: 1;\n}\n@media all and (transform-3d), (-webkit-transform-3d) {\n  .carousel-inner > .item {\n    -webkit-transition: -webkit-transform .6s ease-in-out;\n         -o-transition:      -o-transform .6s ease-in-out;\n            transition:         transform .6s ease-in-out;\n\n    -webkit-backface-visibility: hidden;\n            backface-visibility: hidden;\n    -webkit-perspective: 1000;\n            perspective: 1000;\n  }\n  .carousel-inner > .item.next,\n  .carousel-inner > .item.active.right {\n    left: 0;\n    -webkit-transform: translate3d(100%, 0, 0);\n            transform: translate3d(100%, 0, 0);\n  }\n  .carousel-inner > .item.prev,\n  .carousel-inner > .item.active.left {\n    left: 0;\n    -webkit-transform: translate3d(-100%, 0, 0);\n            transform: translate3d(-100%, 0, 0);\n  }\n  .carousel-inner > .item.next.left,\n  .carousel-inner > .item.prev.right,\n  .carousel-inner > .item.active {\n    left: 0;\n    -webkit-transform: translate3d(0, 0, 0);\n            transform: translate3d(0, 0, 0);\n  }\n}\n.carousel-inner > .active,\n.carousel-inner > .next,\n.carousel-inner > .prev {\n  display: block;\n}\n.carousel-inner > .active {\n  left: 0;\n}\n.carousel-inner > .next,\n.carousel-inner > .prev {\n  position: absolute;\n  top: 0;\n  width: 100%;\n}\n.carousel-inner > .next {\n  left: 100%;\n}\n.carousel-inner > .prev {\n  left: -100%;\n}\n.carousel-inner > .next.left,\n.carousel-inner > .prev.right {\n  left: 0;\n}\n.carousel-inner > .active.left {\n  left: -100%;\n}\n.carousel-inner > .active.right {\n  left: 100%;\n}\n.carousel-control {\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  left: 0;\n  width: 15%;\n  font-size: 20px;\n  color: #fff;\n  text-align: center;\n  text-shadow: 0 1px 2px rgba(0, 0, 0, .6);\n  filter: alpha(opacity=50);\n  opacity: .5;\n}\n.carousel-control.left {\n  background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%);\n  background-image:      -o-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%);\n  background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .5)), to(rgba(0, 0, 0, .0001)));\n  background-image:         linear-gradient(to right, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);\n  background-repeat: repeat-x;\n}\n.carousel-control.right {\n  right: 0;\n  left: auto;\n  background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%);\n  background-image:      -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%);\n  background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .0001)), to(rgba(0, 0, 0, .5)));\n  background-image:         linear-gradient(to right, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);\n  background-repeat: repeat-x;\n}\n.carousel-control:hover,\n.carousel-control:focus {\n  color: #fff;\n  text-decoration: none;\n  filter: alpha(opacity=90);\n  outline: 0;\n  opacity: .9;\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-left,\n.carousel-control .glyphicon-chevron-right {\n  position: absolute;\n  top: 50%;\n  z-index: 5;\n  display: inline-block;\n}\n.carousel-control .icon-prev,\n.carousel-control .glyphicon-chevron-left {\n  left: 50%;\n  margin-left: -10px;\n}\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-right {\n  right: 50%;\n  margin-right: -10px;\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next {\n  width: 20px;\n  height: 20px;\n  margin-top: -10px;\n  font-family: serif;\n  line-height: 1;\n}\n.carousel-control .icon-prev:before {\n  content: '\\2039';\n}\n.carousel-control .icon-next:before {\n  content: '\\203a';\n}\n.carousel-indicators {\n  position: absolute;\n  bottom: 10px;\n  left: 50%;\n  z-index: 15;\n  width: 60%;\n  padding-left: 0;\n  margin-left: -30%;\n  text-align: center;\n  list-style: none;\n}\n.carousel-indicators li {\n  display: inline-block;\n  width: 10px;\n  height: 10px;\n  margin: 1px;\n  text-indent: -999px;\n  cursor: pointer;\n  background-color: #000 \\9;\n  background-color: rgba(0, 0, 0, 0);\n  border: 1px solid #fff;\n  border-radius: 10px;\n}\n.carousel-indicators .active {\n  width: 12px;\n  height: 12px;\n  margin: 0;\n  background-color: #fff;\n}\n.carousel-caption {\n  position: absolute;\n  right: 15%;\n  bottom: 20px;\n  left: 15%;\n  z-index: 10;\n  padding-top: 20px;\n  padding-bottom: 20px;\n  color: #fff;\n  text-align: center;\n  text-shadow: 0 1px 2px rgba(0, 0, 0, .6);\n}\n.carousel-caption .btn {\n  text-shadow: none;\n}\n@media screen and (min-width: 768px) {\n  .carousel-control .glyphicon-chevron-left,\n  .carousel-control .glyphicon-chevron-right,\n  .carousel-control .icon-prev,\n  .carousel-control .icon-next {\n    width: 30px;\n    height: 30px;\n    margin-top: -15px;\n    font-size: 30px;\n  }\n  .carousel-control .glyphicon-chevron-left,\n  .carousel-control .icon-prev {\n    margin-left: -15px;\n  }\n  .carousel-control .glyphicon-chevron-right,\n  .carousel-control .icon-next {\n    margin-right: -15px;\n  }\n  .carousel-caption {\n    right: 20%;\n    left: 20%;\n    padding-bottom: 30px;\n  }\n  .carousel-indicators {\n    bottom: 20px;\n  }\n}\n.clearfix:before,\n.clearfix:after,\n.dl-horizontal dd:before,\n.dl-horizontal dd:after,\n.container:before,\n.container:after,\n.container-fluid:before,\n.container-fluid:after,\n.row:before,\n.row:after,\n.form-horizontal .form-group:before,\n.form-horizontal .form-group:after,\n.btn-toolbar:before,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:before,\n.btn-group-vertical > .btn-group:after,\n.nav:before,\n.nav:after,\n.navbar:before,\n.navbar:after,\n.navbar-header:before,\n.navbar-header:after,\n.navbar-collapse:before,\n.navbar-collapse:after,\n.pager:before,\n.pager:after,\n.panel-body:before,\n.panel-body:after,\n.modal-footer:before,\n.modal-footer:after {\n  display: table;\n  content: \" \";\n}\n.clearfix:after,\n.dl-horizontal dd:after,\n.container:after,\n.container-fluid:after,\n.row:after,\n.form-horizontal .form-group:after,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:after,\n.nav:after,\n.navbar:after,\n.navbar-header:after,\n.navbar-collapse:after,\n.pager:after,\n.panel-body:after,\n.modal-footer:after {\n  clear: both;\n}\n.center-block {\n  display: block;\n  margin-right: auto;\n  margin-left: auto;\n}\n.pull-right {\n  float: right !important;\n}\n.pull-left {\n  float: left !important;\n}\n.hide {\n  display: none !important;\n}\n.show {\n  display: block !important;\n}\n.invisible {\n  visibility: hidden;\n}\n.text-hide {\n  font: 0/0 a;\n  color: transparent;\n  text-shadow: none;\n  background-color: transparent;\n  border: 0;\n}\n.hidden {\n  display: none !important;\n  visibility: hidden !important;\n}\n.affix {\n  position: fixed;\n}\n@-ms-viewport {\n  width: device-width;\n}\n.visible-xs,\n.visible-sm,\n.visible-md,\n.visible-lg {\n  display: none !important;\n}\n.visible-xs-block,\n.visible-xs-inline,\n.visible-xs-inline-block,\n.visible-sm-block,\n.visible-sm-inline,\n.visible-sm-inline-block,\n.visible-md-block,\n.visible-md-inline,\n.visible-md-inline-block,\n.visible-lg-block,\n.visible-lg-inline,\n.visible-lg-inline-block {\n  display: none !important;\n}\n@media (max-width: 767px) {\n  .visible-xs {\n    display: block !important;\n  }\n  table.visible-xs {\n    display: table;\n  }\n  tr.visible-xs {\n    display: table-row !important;\n  }\n  th.visible-xs,\n  td.visible-xs {\n    display: table-cell !important;\n  }\n}\n@media (max-width: 767px) {\n  .visible-xs-block {\n    display: block !important;\n  }\n}\n@media (max-width: 767px) {\n  .visible-xs-inline {\n    display: inline !important;\n  }\n}\n@media (max-width: 767px) {\n  .visible-xs-inline-block {\n    display: inline-block !important;\n  }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n  .visible-sm {\n    display: block !important;\n  }\n  table.visible-sm {\n    display: table;\n  }\n  tr.visible-sm {\n    display: table-row !important;\n  }\n  th.visible-sm,\n  td.visible-sm {\n    display: table-cell !important;\n  }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n  .visible-sm-block {\n    display: block !important;\n  }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n  .visible-sm-inline {\n    display: inline !important;\n  }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n  .visible-sm-inline-block {\n    display: inline-block !important;\n  }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n  .visible-md {\n    display: block !important;\n  }\n  table.visible-md {\n    display: table;\n  }\n  tr.visible-md {\n    display: table-row !important;\n  }\n  th.visible-md,\n  td.visible-md {\n    display: table-cell !important;\n  }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n  .visible-md-block {\n    display: block !important;\n  }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n  .visible-md-inline {\n    display: inline !important;\n  }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n  .visible-md-inline-block {\n    display: inline-block !important;\n  }\n}\n@media (min-width: 1200px) {\n  .visible-lg {\n    display: block !important;\n  }\n  table.visible-lg {\n    display: table;\n  }\n  tr.visible-lg {\n    display: table-row !important;\n  }\n  th.visible-lg,\n  td.visible-lg {\n    display: table-cell !important;\n  }\n}\n@media (min-width: 1200px) {\n  .visible-lg-block {\n    display: block !important;\n  }\n}\n@media (min-width: 1200px) {\n  .visible-lg-inline {\n    display: inline !important;\n  }\n}\n@media (min-width: 1200px) {\n  .visible-lg-inline-block {\n    display: inline-block !important;\n  }\n}\n@media (max-width: 767px) {\n  .hidden-xs {\n    display: none !important;\n  }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n  .hidden-sm {\n    display: none !important;\n  }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n  .hidden-md {\n    display: none !important;\n  }\n}\n@media (min-width: 1200px) {\n  .hidden-lg {\n    display: none !important;\n  }\n}\n.visible-print {\n  display: none !important;\n}\n@media print {\n  .visible-print {\n    display: block !important;\n  }\n  table.visible-print {\n    display: table;\n  }\n  tr.visible-print {\n    display: table-row !important;\n  }\n  th.visible-print,\n  td.visible-print {\n    display: table-cell !important;\n  }\n}\n.visible-print-block {\n  display: none !important;\n}\n@media print {\n  .visible-print-block {\n    display: block !important;\n  }\n}\n.visible-print-inline {\n  display: none !important;\n}\n@media print {\n  .visible-print-inline {\n    display: inline !important;\n  }\n}\n.visible-print-inline-block {\n  display: none !important;\n}\n@media print {\n  .visible-print-inline-block {\n    display: inline-block !important;\n  }\n}\n@media print {\n  .hidden-print {\n    display: none !important;\n  }\n}\n/*# sourceMappingURL=bootstrap.css.map */\n"
  },
  {
    "path": "css/hux-blog.css",
    "content": "/*!\n * Hux Blog v1.7.0 (http://huxpro.github.io)\n * Copyright 2018 Hux <huxpro@gmail.com>\n */\n\n@media (min-width: 1200px) {\n  .post-container,\n  .sidebar-container {\n    padding-right: 5%;\n  }\n}\n@media (min-width: 768px) {\n  .post-container {\n    padding-right: 5%;\n  }\n}\n.sidebar-container {\n  color: #bfbfbf;\n  font-size: 14px;\n}\n.sidebar-container h5 {\n  color: #cccccc;\n  padding-bottom: 1em;\n}\n.sidebar-container h5 a {\n  color: #cccccc !important;\n  text-decoration: none;\n}\n.sidebar-container a {\n  color: #bfbfbf !important;\n}\n.sidebar-container a:hover,\n.sidebar-container a:active {\n  color: #0085a1 !important;\n}\n.sidebar-container .tags a {\n  border-color: #bfbfbf;\n}\n.sidebar-container .tags a:hover,\n.sidebar-container .tags a:active {\n  border-color: #0085a1;\n}\n.sidebar-container .short-about img {\n  width: 80%;\n  display: block;\n  border-radius: 5px;\n  margin-bottom: 20px;\n}\n.sidebar-container .short-about p {\n  margin-top: 0px;\n  margin-bottom: 20px;\n}\n.sidebar-container .short-about .list-inline > li {\n  padding-left: 0px;\n}\n.catalog-container {\n  padding: 0px;\n}\n.side-catalog {\n  display: block;\n  overflow: auto;\n  height: 100%;\n  padding-bottom: 40px;\n  width: 195px;\n}\n.side-catalog.fixed {\n  position: fixed;\n  top: -21px;\n}\n.side-catalog.fold .catalog-toggle::before {\n  content: \"+\";\n}\n.side-catalog.fold .catalog-body {\n  display: none;\n}\n.side-catalog .catalog-toggle::before {\n  content: \"−\";\n  position: relative;\n  margin-right: 5px;\n  bottom: 1px;\n}\n.side-catalog .catalog-body {\n  position: relative;\n  list-style: none;\n  height: auto;\n  overflow: hidden;\n  padding-left: 0px;\n  padding-right: 5px;\n  text-indent: 0;\n}\n.side-catalog .catalog-body li {\n  position: relative;\n  list-style: none;\n}\n.side-catalog .catalog-body li a {\n  padding-left: 10px;\n  max-width: 180px;\n  display: inline-block;\n  vertical-align: middle;\n  height: 30px;\n  line-height: 30px;\n  overflow: hidden;\n  text-decoration: none;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n}\n.side-catalog .catalog-body .h1_nav,\n.side-catalog .catalog-body .h2_nav,\n.side-catalog .catalog-body .h3_nav {\n  margin-left: 0;\n  font-size: 13px;\n  font-weight: bold;\n}\n.side-catalog .catalog-body .h4_nav,\n.side-catalog .catalog-body .h5_nav,\n.side-catalog .catalog-body .h6_nav {\n  margin-left: 10px;\n  font-size: 12px;\n}\n.side-catalog .catalog-body .h4_nav a,\n.side-catalog .catalog-body .h5_nav a,\n.side-catalog .catalog-body .h6_nav a {\n  max-width: 170px;\n}\n.side-catalog .catalog-body .active {\n  border-radius: 4px;\n  background-color: #F5F5F5;\n}\n.side-catalog .catalog-body .active a {\n  color: #0085a1!important;\n}\n@media (max-width: 1200px) {\n  .side-catalog {\n    display: none;\n  }\n}\n/*\n  Please note this CSS is currently in   prototype form. We'll implement a cleaned up version in Web Starter Kit.\n*/\n.paper-snackbar {\n  transition-property: opacity, bottom, left, right, width, margin, border-radius;\n  transition-duration: 0.5s;\n  transition-timing-function: ease;\n  /*font-family: RobotoDraft;*/\n  font-size: 14px;\n  min-height: 14px;\n  background-color: #323232;\n  background-color: #0085a1;\n  position: fixed;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  color: white;\n  line-height: 22px;\n  padding: 18px 24px;\n  bottom: 0px;\n  opacity: 0;\n}\n@media (min-width: 640px) {\n  .paper-snackbar {\n    /*\n    Desktop:\n      Single-line snackbar height: 48 dp tall\n      Minimum width: 288 dp\n      Maximum width: 568 dp\n      2 dp rounded corner\n      Text: Roboto Regular 14 sp\n      Action button: Roboto Medium 14 sp, all-caps text\n      Default background fill: #323232 100%\n    */\n    min-width: 288px;\n    max-width: 568px;\n    display: inline-flex;\n    border-radius: 2px;\n    margin: 24px;\n    bottom: -100px;\n  }\n}\n@media (max-width: 640px) {\n  .paper-snackbar {\n    /*\n  Mobile:\n    Single-line snackbar height: 48 dp\n    Multi-line snackbar height: 80 dp\n    Text: Roboto Regular 14 sp\n    Action button: Roboto Medium 14 sp, all-caps text\n    Default background fill: #323232 100%  \n  */\n    left: 0px;\n    right: 0px;\n  }\n}\n.paper-snackbar .action {\n  background: inherit;\n  display: inline-block;\n  border: none;\n  font-size: inherit;\n  text-transform: uppercase;\n  color: #ffeb3b;\n  margin: 0px 0px 0px 24px;\n  padding: 0px;\n  min-width: min-content;\n  outline: 0px;\n}\n/* Everything from here down is actually just for the demo - the material buttons and card, and various other pieces of styling */\n/* Variables */\n/* Buttons */\n.paper-button {\n  position: relative;\n  padding: 4px 0;\n  margin: 1em;\n  width: 160px;\n  overflow: hidden;\n  user-select: none;\n  color: #000000;\n  text-transform: uppercase;\n  border-radius: 3px;\n  outline-color: #cccccc;\n}\n.paper-button:hover {\n  cursor: pointer;\n}\n.paper-button button,\n.paper-button input[type=\"submit\"] {\n  background: inherit;\n  border: none;\n  display: block;\n  width: 100%;\n  height: 100%;\n  /*font-family: 'Roboto';*/\n  font-size: 1em;\n  color: #000000;\n  text-transform: uppercase;\n}\n.paper-button.colored,\n.paper-button.colored button {\n  color: #4285f4;\n}\n.paper-button.raised-button.colored {\n  background-color: #4285f4;\n}\n.paper-button .raised-button {\n  box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26);\n}\n.paper-button.raised-button.colored {\n  background: #4285f4;\n  color: #fff;\n}\n.paper-button[disabled] {\n  background-color: #EAEAEA !important;\n  color: #A8A8A8 !important;\n  cursor: auto;\n}\n.paper-button:hover {\n  background-color: #EAEAEA;\n}\n.paper-button.raised-button.colored:hover {\n  background-color: #3367d6;\n}\nbutton.paper-button {\n  border: 0;\n  /*font-family: RobotoDraft, 'Helvetica Neue', Helvetica, Arial;*/\n  font-size: 1em;\n  line-height: 25px;\n  background-color: #ffffff;\n}\n.paper-button input[type=\"submit\"] {\n  outline-color: #cccccc;\n}\n.paper-button.colored.raised-button input[type=\"submit\"] {\n  color: #ffffff;\n}\n/** Shadows **/\n.paper-shadow-animated.paper-shadow {\n  transition: box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1);\n}\n.paper-shadow-top-z-1 {\n  box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.16);\n}\n.paper-shadow-bottom-z-1 {\n  box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26);\n}\n.paper-shadow-top-z-2 {\n  box-shadow: 0 6px 20px 0 rgba(0, 0, 0, 0.19);\n}\n.paper-shadow-bottom-z-2 {\n  box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2);\n}\n.paper-shadow-top-z-3 {\n  box-shadow: 0 17px 50px 0 rgba(0, 0, 0, 0.19);\n}\n.paper-shadow-bottom-z-3 {\n  box-shadow: 0 12px 15px 0 rgba(0, 0, 0, 0.24);\n}\n.paper-shadow-top-z-4 {\n  box-shadow: 0 25px 55px 0 rgba(0, 0, 0, 0.21);\n}\n/** Card **/\n.card {\n  background: white;\n  width: 300px;\n  height: 300px;\n  position: relative;\n  margin: 16px;\n  border-radius: 2px;\n}\n.highlight,\npre.highlight {\n  background: #282c34;\n  color: #abb2bf;\n}\n.highlight pre {\n  background: #282c34;\n}\n.highlight .hll {\n  background: #282c34;\n}\n.highlight .c {\n  color: #5c6370;\n  font-style: italic;\n}\n.highlight .err {\n  color: #960050;\n  background-color: #1e0010;\n}\n.highlight .k {\n  color: #c678dd;\n}\n.highlight .l {\n  color: #98c379;\n}\n.highlight .n {\n  color: #abb2bf;\n}\n.highlight .o {\n  color: #abb2bf;\n}\n.highlight .p {\n  color: #abb2bf;\n}\n.highlight .cm {\n  color: #5c6370;\n  font-style: italic;\n}\n.highlight .cp {\n  color: #5c6370;\n  font-style: italic;\n}\n.highlight .c1 {\n  color: #5c6370;\n  font-style: italic;\n}\n.highlight .cs {\n  color: #5c6370;\n  font-style: italic;\n}\n.highlight .ge {\n  font-style: italic;\n}\n.highlight .gs {\n  font-weight: 700;\n}\n.highlight .kc {\n  color: #c678dd;\n}\n.highlight .kd {\n  color: #c678dd;\n}\n.highlight .kn {\n  color: #c678dd;\n}\n.highlight .kp {\n  color: #c678dd;\n}\n.highlight .kr {\n  color: #c678dd;\n}\n.highlight .kt {\n  color: #c678dd;\n}\n.highlight .ld {\n  color: #98c379;\n}\n.highlight .m {\n  color: #d19a66;\n}\n.highlight .s {\n  color: #98c379;\n}\n.highlight .na {\n  color: #d19a66;\n}\n.highlight .nb {\n  color: #e5c07b;\n}\n.highlight .nc {\n  color: #e5c07b;\n}\n.highlight .no {\n  color: #e5c07b;\n}\n.highlight .nd {\n  color: #e5c07b;\n}\n.highlight .ni {\n  color: #e5c07b;\n}\n.highlight .ne {\n  color: #e5c07b;\n}\n.highlight .nf {\n  color: #abb2bf;\n}\n.highlight .nl {\n  color: #e5c07b;\n}\n.highlight .nn {\n  color: #abb2bf;\n}\n.highlight .nx {\n  color: #abb2bf;\n}\n.highlight .py {\n  color: #e5c07b;\n}\n.highlight .nt {\n  color: #e06c75;\n}\n.highlight .nv {\n  color: #e5c07b;\n}\n.highlight .ow {\n  font-weight: 700;\n}\n.highlight .w {\n  color: #f8f8f2;\n}\n.highlight .mf {\n  color: #d19a66;\n}\n.highlight .mh {\n  color: #d19a66;\n}\n.highlight .mi {\n  color: #d19a66;\n}\n.highlight .mo {\n  color: #d19a66;\n}\n.highlight .sb {\n  color: #98c379;\n}\n.highlight .sc {\n  color: #98c379;\n}\n.highlight .sd {\n  color: #98c379;\n}\n.highlight .s2 {\n  color: #98c379;\n}\n.highlight .se {\n  color: #98c379;\n}\n.highlight .sh {\n  color: #98c379;\n}\n.highlight .si {\n  color: #98c379;\n}\n.highlight .sx {\n  color: #98c379;\n}\n.highlight .sr {\n  color: #56b6c2;\n}\n.highlight .s1 {\n  color: #98c379;\n}\n.highlight .ss {\n  color: #56b6c2;\n}\n.highlight .bp {\n  color: #e5c07b;\n}\n.highlight .vc {\n  color: #e5c07b;\n}\n.highlight .vg {\n  color: #e5c07b;\n}\n.highlight .vi {\n  color: #e06c75;\n}\n.highlight .il {\n  color: #d19a66;\n}\n.highlight .gu {\n  color: #75715e;\n}\n.highlight .gd {\n  color: #f92672;\n}\n.highlight .gi {\n  color: #a6e22e;\n}\n.highlighter-rouge .highlight {\n  margin-bottom: 10px;\n  border-radius: 6px;\n}\n.highlighter-rouge .highlight pre {\n  font-size: 14px;\n  line-height: 1.5;\n  color: #555;\n  background: transparent;\n  border: 0;\n  margin: 0;\n  padding: 0;\n  word-wrap: normal;\n}\n.highlighter-rouge .highlight .rouge-code pre {\n  color: #abb2bf;\n}\n.highlighter-rouge .highlight .table-responsive {\n  margin: 0px;\n  border: 0px;\n}\n.highlighter-rouge .highlight .table {\n  margin: 0px;\n  table-layout: fixed;\n}\n.highlighter-rouge .highlight table > tbody > tr > td {\n  margin: 0;\n  border: 0;\n  padding: 0;\n}\n.highlighter-rouge .highlight table > tbody > tr > td > pre {\n  padding: 14px;\n}\n.highlighter-rouge .highlight td.rouge-gutter {\n  width: 56px;\n}\n.highlighter-rouge .highlight .lineno {\n  text-align: right;\n  border-radius: 0px;\n}\n/* Landscape phones and down */\n@media (max-width: 480px) {\n  .highlighter-rouge .highlight {\n    margin-left: -15px;\n    margin-right: -15px;\n    border-radius: 0px;\n  }\n  .highlighter-rouge .highlight td.rouge-gutter {\n    width: 46px;\n  }\n}\nbody {\n  /* Hux learn from\n     *     TypeIsBeautiful,\n     *     [This Post](http://zhuanlan.zhihu.com/ibuick/20186806) etc.\n     */\n  font-family: -apple-system, BlinkMacSystemFont, \"Helvetica Neue\", \"Arial\", \"PingFang SC\", \"Hiragino Sans GB\", \"STHeiti\", \"Microsoft YaHei\", \"Microsoft JhengHei\", \"Source Han Sans SC\", \"Noto Sans CJK SC\", \"Source Han Sans CN\", \"Noto Sans SC\", \"Source Han Sans TC\", \"Noto Sans CJK TC\", \"WenQuanYi Micro Hei\", SimSun, sans-serif;\n  line-height: 1.7;\n  font-size: 16px;\n  color: #404040;\n  overflow-x: hidden;\n  text-rendering: auto;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\np {\n  margin: 30px 0;\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n  /* Hux learn from\n     *     TypeIsBeautiful,\n     *     [This Post](http://zhuanlan.zhihu.com/ibuick/20186806) etc.\n     */\n  font-family: -apple-system, BlinkMacSystemFont, \"Helvetica Neue\", \"Arial\", \"PingFang SC\", \"Hiragino Sans GB\", \"STHeiti\", \"Microsoft YaHei\", \"Microsoft JhengHei\", \"Source Han Sans SC\", \"Noto Sans CJK SC\", \"Source Han Sans CN\", \"Noto Sans SC\", \"Source Han Sans TC\", \"Noto Sans CJK TC\", \"WenQuanYi Micro Hei\", SimSun, sans-serif;\n  line-height: 1.7;\n  line-height: 1.1;\n  font-weight: bold;\n}\nh4 {\n  font-size: 21px;\n}\na {\n  color: #404040;\n}\na:hover,\na:focus {\n  color: #0085a1;\n}\na img:hover,\na img:focus {\n  cursor: zoom-in;\n}\narticle {\n  overflow: hidden;\n}\nblockquote {\n  color: gray;\n  font-style: italic;\n  font-size: 0.95em;\n  margin: 20px 0 20px;\n}\nblockquote p {\n  margin: 0;\n}\nsmall.img-hint {\n  display: block;\n  margin-top: -20px;\n  text-align: center;\n}\nbr + small.img-hint {\n  margin-top: -40px;\n}\nimg.shadow {\n  box-shadow: rgba(0, 0, 0, 0.258824) 0px 2px 5px 0px;\n}\nselect {\n  -webkit-appearance: none;\n  margin-top: 15px;\n  color: #337ab7;\n  border-color: #337ab7;\n  padding: 0em 0.4em;\n  background: white;\n}\nselect.sel-lang {\n  min-height: 28px;\n  font-size: 14px;\n}\ntable.table > tbody th,\ntable.table > thead th,\ntable.table > tbody td,\ntable.table > thead td {\n  border: 1px solid #eee;\n}\nhr.small {\n  max-width: 100px;\n  margin: 15px auto;\n  border-width: 4px;\n  border-color: white;\n}\npre,\n.table-responsive {\n  -webkit-overflow-scrolling: touch;\n}\npre code {\n  display: block;\n  width: auto;\n  white-space: pre;\n  word-wrap: normal;\n}\ncode,\nkbd,\npre,\nsamp {\n  font-family: \"Fira Code\", Menlo, Monaco, Consolas, \"Courier New\", monospace;\n}\n.postlist-container {\n  margin-bottom: 15px;\n}\n.post-container a {\n  color: #337ab7;\n}\n.post-container a:hover,\n.post-container a:focus {\n  color: #0085a1;\n}\n.post-container h1,\n.post-container h2,\n.post-container h3,\n.post-container h4,\n.post-container h5,\n.post-container h6 {\n  margin: 30px 0 10px;\n  line-height: 1.4;\n}\n.post-container h5 {\n  font-size: 19px;\n  font-weight: 600;\n  color: gray;\n}\n.post-container h5 + p {\n  margin-top: 5px;\n}\n.post-container h6 {\n  font-size: 16px;\n  font-weight: 600;\n  color: gray;\n}\n.post-container h6 + p {\n  margin-top: 5px;\n}\n@media screen and (max-width: 768px) {\n  .post-container h1 {\n    font-size: 30px;\n  }\n  .post-container h2 {\n    font-size: 24px;\n  }\n  .post-container h3 {\n    font-size: 21px;\n  }\n  .post-container h4 {\n    font-size: 19px;\n  }\n}\n.post-container ul,\n.post-container ol {\n  margin-bottom: 40px;\n}\n@media screen and (max-width: 768px) {\n  .post-container ul,\n  .post-container ol {\n    padding-left: 30px;\n  }\n}\n@media screen and (max-width: 500px) {\n  .post-container ul,\n  .post-container ol {\n    padding-left: 20px;\n  }\n}\n.post-container ol ol,\n.post-container ol ul,\n.post-container ul ol,\n.post-container ul ul {\n  margin-bottom: 5px;\n}\n.post-container li p {\n  margin: 0;\n  margin-bottom: 5px;\n}\n.post-container li h1,\n.post-container li h2,\n.post-container li h3,\n.post-container li h4,\n.post-container li h5,\n.post-container li h6 {\n  line-height: 2;\n  margin-top: 20px;\n}\n.post-container .pager li {\n  width: 48%;\n}\n.post-container .pager li.next {\n  float: right;\n}\n.post-container .pager li.previous {\n  float: left;\n}\n.post-container .pager li > a {\n  width: 100%;\n}\n.post-container .pager li > a > span {\n  color: #cccccc;\n  font-weight: normal;\n  letter-spacing: 0.5px;\n}\n.post-container .anchorjs-link {\n  position: absolute;\n  padding-top: 1px;\n}\n@media only screen and (max-width: 767px) {\n  /**\n\t * Layout\n\t * Since V1.6 we use absolute positioning to prevent to expand container-fluid\n\t * which would cover tags. A absolute positioning make a new layer.\n\t */\n  .navbar-default .navbar-collapse {\n    position: absolute;\n    right: 0;\n    border: none;\n    background: white;\n    box-shadow: 0px 5px 10px 2px rgba(0, 0, 0, 0.2);\n    box-shadow: rgba(0, 0, 0, 0.117647) 0px 1px 6px, rgba(0, 0, 0, 0.239216) 0px 1px 4px;\n    border-radius: 2px;\n    width: 170px;\n  }\n  /**\n\t * Animation\n\t * HuxBlog-Navbar using genuine Material Design Animation\n\t */\n  #huxblog_navbar {\n    /**\n\t\t * Sharable code and 'out' function\n\t\t */\n    opacity: 0;\n    transform: scaleX(0);\n    transform-origin: top right;\n    transition: all 200ms cubic-bezier(0.47, 0, 0.4, 0.99) 0ms;\n    -webkit-transform: scaleX(0);\n    -webkit-transform-origin: top right;\n    -webkit-transition: all 200ms cubic-bezier(0.47, 0, 0.4, 0.99) 0ms;\n    /**\n\t\t *'In' Animation\n\t\t */\n  }\n  #huxblog_navbar a {\n    font-size: 13px;\n    line-height: 28px;\n  }\n  #huxblog_navbar .navbar-collapse {\n    height: 0px;\n    transform: scaleY(0);\n    transform-origin: top right;\n    transition: transform 400ms cubic-bezier(0.32, 1, 0.23, 1) 0ms;\n    -webkit-transform: scaleY(0);\n    -webkit-transform-origin: top right;\n    -webkit-transition: -webkit-transform 400ms cubic-bezier(0.32, 1, 0.23, 1) 0ms;\n  }\n  #huxblog_navbar li {\n    opacity: 0;\n    transition: opacity 100ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;\n    -webkit-transition: opacity 100ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;\n  }\n  #huxblog_navbar.in {\n    transform: scaleX(1);\n    -webkit-transform: scaleX(1);\n    opacity: 1;\n    transition: all 250ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;\n    -webkit-transition: all 250ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;\n  }\n  #huxblog_navbar.in .navbar-collapse {\n    transform: scaleY(1);\n    -webkit-transform: scaleY(1);\n    transition: transform 500ms cubic-bezier(0.23, 1, 0.32, 1);\n    -webkit-transition: -webkit-transform 500ms cubic-bezier(0.23, 1, 0.32, 1);\n  }\n  #huxblog_navbar.in li {\n    opacity: 1;\n    transition: opacity 450ms cubic-bezier(0.23, 1, 0.32, 1) 205ms;\n    -webkit-transition: opacity 450ms cubic-bezier(0.23, 1, 0.32, 1) 205ms;\n  }\n}\n.navbar-custom {\n  background: none;\n  border: none;\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  z-index: 3;\n  /* Hux learn from\n     *     TypeIsBeautiful,\n     *     [This Post](http://zhuanlan.zhihu.com/ibuick/20186806) etc.\n     */\n  font-family: -apple-system, BlinkMacSystemFont, \"Helvetica Neue\", \"Arial\", \"PingFang SC\", \"Hiragino Sans GB\", \"STHeiti\", \"Microsoft YaHei\", \"Microsoft JhengHei\", \"Source Han Sans SC\", \"Noto Sans CJK SC\", \"Source Han Sans CN\", \"Noto Sans SC\", \"Source Han Sans TC\", \"Noto Sans CJK TC\", \"WenQuanYi Micro Hei\", SimSun, sans-serif;\n  line-height: 1.7;\n}\n.navbar-custom .navbar-brand {\n  font-weight: 800;\n  color: white;\n  height: 56px;\n  line-height: 25px;\n}\n.navbar-custom .navbar-brand:hover {\n  color: rgba(255, 255, 255, 0.8);\n}\n.navbar-custom .nav li a {\n  text-transform: uppercase;\n  font-size: 12px;\n  line-height: 20px;\n  font-weight: 800;\n  letter-spacing: 1px;\n}\n.navbar-custom .nav li a:active {\n  background: rgba(0, 0, 0, 0.12);\n}\n@media only screen and (min-width: 768px) {\n  .navbar-custom {\n    background: transparent;\n    border-bottom: 1px solid transparent;\n  }\n  .navbar-custom body {\n    font-size: 20px;\n  }\n  .navbar-custom .navbar-brand {\n    color: white;\n    padding: 20px;\n    line-height: 20px;\n  }\n  .navbar-custom .navbar-brand:hover,\n  .navbar-custom .navbar-brand:focus {\n    color: rgba(255, 255, 255, 0.8);\n  }\n  .navbar-custom .nav li a {\n    color: white;\n    padding: 20px;\n  }\n  .navbar-custom .nav li a:hover,\n  .navbar-custom .nav li a:focus {\n    color: rgba(255, 255, 255, 0.8);\n  }\n  .navbar-custom .nav li a:active {\n    background: none;\n  }\n}\n.navbar-custom.invert .navbar-toggle:active {\n  background-color: rgba(0, 0, 0, 0.05);\n}\n.navbar-custom.invert .navbar-toggle .icon-bar {\n  background-color: #404040;\n}\n.navbar-custom.invert .navbar-brand {\n  color: #404040;\n}\n.navbar-custom.invert .navbar-brand:hover,\n.navbar-custom.invert .navbar-brand:focus {\n  color: #0085a1;\n}\n.navbar-custom.invert .nav li a {\n  color: #404040;\n}\n.navbar-custom.invert .nav li a:hover,\n.navbar-custom.invert .nav li a:focus {\n  color: #0085a1;\n}\n@media only screen and (min-width: 1170px) {\n  .navbar-custom {\n    -webkit-transition: background-color 0.3s;\n    -moz-transition: background-color 0.3s;\n    transition: background-color 0.3s;\n    /* Force Hardware Acceleration in WebKit */\n    -webkit-transform: translate3d(0, 0, 0);\n    -moz-transform: translate3d(0, 0, 0);\n    -ms-transform: translate3d(0, 0, 0);\n    -o-transform: translate3d(0, 0, 0);\n    transform: translate3d(0, 0, 0);\n    -webkit-backface-visibility: hidden;\n    backface-visibility: hidden;\n  }\n  .navbar-custom.is-fixed {\n    /* when the user scrolls down, we hide the header right above the viewport */\n    position: fixed;\n    top: -61px;\n    background-color: rgba(255, 255, 255, 0.9);\n    border-bottom: 1px solid #f2f2f2;\n    -webkit-transition: -webkit-transform 0.3s;\n    -moz-transition: -moz-transform 0.3s;\n    transition: transform 0.3s;\n  }\n  .navbar-custom.is-fixed .navbar-brand {\n    color: #404040;\n  }\n  .navbar-custom.is-fixed .navbar-brand:hover,\n  .navbar-custom.is-fixed .navbar-brand:focus {\n    color: #0085a1;\n  }\n  .navbar-custom.is-fixed .nav li a {\n    color: #404040;\n  }\n  .navbar-custom.is-fixed .nav li a:hover,\n  .navbar-custom.is-fixed .nav li a:focus {\n    color: #0085a1;\n  }\n  .navbar-custom.is-visible {\n    /* if the user changes the scrolling direction, we show the header */\n    -webkit-transform: translate3d(0, 100%, 0);\n    -moz-transform: translate3d(0, 100%, 0);\n    -ms-transform: translate3d(0, 100%, 0);\n    -o-transform: translate3d(0, 100%, 0);\n    transform: translate3d(0, 100%, 0);\n  }\n}\n.intro-header {\n  background: no-repeat center center;\n  background-color: #cccccc;\n  background-attachment: scroll;\n  -webkit-background-size: cover;\n  -moz-background-size: cover;\n  background-size: cover;\n  -o-background-size: cover;\n  margin-bottom: 0px;\n  /* 0 on mobile, modify by Hux */\n}\n.intro-header.style-text {\n  background: none;\n}\n.intro-header.style-text .site-heading,\n.intro-header.style-text .post-heading,\n.intro-header.style-text .page-heading {\n  padding: 85px 0 20px;\n  color: #404040;\n}\n.intro-header.style-text .site-heading .subheading,\n.intro-header.style-text .post-heading .subheading,\n.intro-header.style-text .page-heading .subheading {\n  margin-bottom: 15px;\n}\n.intro-header.style-text .tags a,\n.intro-header.style-text .tags .tag {\n  border-color: #404040;\n  color: #404040;\n}\n.intro-header.style-text .tags a:hover,\n.intro-header.style-text .tags .tag:hover,\n.intro-header.style-text .tags a:active,\n.intro-header.style-text .tags .tag:active {\n  background-color: rgba(0, 0, 0, 0.05);\n}\n@media only screen and (min-width: 768px) {\n  .intro-header {\n    margin-bottom: 20px;\n    /* response on desktop */\n  }\n}\n.intro-header .site-heading,\n.intro-header .post-heading,\n.intro-header .page-heading {\n  padding: 85px 0 55px;\n  color: white;\n}\n@media only screen and (min-width: 768px) {\n  .intro-header .site-heading,\n  .intro-header .post-heading,\n  .intro-header .page-heading {\n    padding: 150px 0;\n  }\n}\n.intro-header .site-heading {\n  padding: 95px 0 70px;\n}\n@media only screen and (min-width: 768px) {\n  .intro-header .site-heading {\n    padding: 150px 0;\n  }\n}\n.intro-header .site-heading,\n.intro-header .page-heading {\n  text-align: center;\n}\n.intro-header .site-heading h1,\n.intro-header .page-heading h1 {\n  margin-top: 0;\n  font-size: 50px;\n}\n.intro-header .site-heading .subheading,\n.intro-header .page-heading .subheading {\n  /* Hux learn from\n     *     TypeIsBeautiful,\n     *     [This Post](http://zhuanlan.zhihu.com/ibuick/20186806) etc.\n     */\n  font-family: -apple-system, BlinkMacSystemFont, \"Helvetica Neue\", \"Arial\", \"PingFang SC\", \"Hiragino Sans GB\", \"STHeiti\", \"Microsoft YaHei\", \"Microsoft JhengHei\", \"Source Han Sans SC\", \"Noto Sans CJK SC\", \"Source Han Sans CN\", \"Noto Sans SC\", \"Source Han Sans TC\", \"Noto Sans CJK TC\", \"WenQuanYi Micro Hei\", SimSun, sans-serif;\n  line-height: 1.7;\n  font-size: 18px;\n  line-height: 1.1;\n  display: block;\n  font-weight: 300;\n  margin: 10px 0 0;\n}\n@media only screen and (min-width: 768px) {\n  .intro-header .site-heading h1,\n  .intro-header .page-heading h1 {\n    font-size: 58px;\n  }\n}\n.intro-header .post-heading h1 {\n  font-size: 30px;\n  margin-bottom: 24px;\n}\n.intro-header .post-heading .subheading,\n.intro-header .post-heading .meta {\n  line-height: 1.1;\n  display: block;\n}\n.intro-header .post-heading .subheading {\n  /* Hux learn from\n     *     TypeIsBeautiful,\n     *     [This Post](http://zhuanlan.zhihu.com/ibuick/20186806) etc.\n     */\n  font-family: -apple-system, BlinkMacSystemFont, \"Helvetica Neue\", \"Arial\", \"PingFang SC\", \"Hiragino Sans GB\", \"STHeiti\", \"Microsoft YaHei\", \"Microsoft JhengHei\", \"Source Han Sans SC\", \"Noto Sans CJK SC\", \"Source Han Sans CN\", \"Noto Sans SC\", \"Source Han Sans TC\", \"Noto Sans CJK TC\", \"WenQuanYi Micro Hei\", SimSun, sans-serif;\n  line-height: 1.7;\n  font-size: 17px;\n  line-height: 1.4;\n  font-weight: normal;\n  margin: 10px 0 30px;\n  margin-top: -5px;\n}\n.intro-header .post-heading .meta {\n  font-family: 'Lora', 'Times New Roman', serif;\n  font-style: italic;\n  font-weight: 300;\n  font-size: 16px;\n}\n.intro-header .post-heading .meta a {\n  color: white;\n}\n@media only screen and (min-width: 768px) {\n  .intro-header .post-heading h1 {\n    font-size: 55px;\n  }\n  .intro-header .post-heading .subheading {\n    font-size: 30px;\n  }\n  .intro-header .post-heading .meta {\n    font-size: 20px;\n  }\n}\n.intro-header .header-img-credit {\n  position: absolute;\n  bottom: 6px;\n  right: 9px;\n  color: white;\n  opacity: 0.3;\n  font-size: 10px;\n  z-index: 1;\n}\n.intro-header .header-img-credit a {\n  color: white;\n}\n@media only screen and (min-width: 768px) {\n  .intro-header .header-img-credit {\n    font-size: 12px;\n    bottom: 10px;\n    right: 15px;\n  }\n}\n.post-preview > a {\n  color: #404040;\n}\n.post-preview > a:hover,\n.post-preview > a:focus {\n  text-decoration: none;\n  color: #0085a1;\n}\n.post-preview > a > .post-title {\n  font-size: 21px;\n  line-height: 1.3;\n  margin-top: 30px;\n  margin-bottom: 8px;\n}\n.post-preview > a > .post-subtitle {\n  font-size: 15px;\n  line-height: 1.3;\n  margin: 0;\n  font-weight: 300;\n  margin-bottom: 10px;\n}\n.post-preview > .post-meta {\n  font-family: 'Lora', 'Times New Roman', serif;\n  color: #cccccc;\n  font-size: 16px;\n  font-style: italic;\n  margin-top: 0;\n}\n.post-preview > .post-meta > a {\n  text-decoration: none;\n  color: #404040;\n}\n.post-preview > .post-meta > a:hover,\n.post-preview > .post-meta > a:focus {\n  color: #0085a1;\n  text-decoration: underline;\n}\n@media only screen and (min-width: 768px) {\n  .post-preview > a > .post-title {\n    font-size: 26px;\n    line-height: 1.3;\n    margin-bottom: 10px;\n  }\n  .post-preview > a > .post-subtitle {\n    font-size: 16px;\n  }\n  .post-preview .post-meta {\n    font-size: 18px;\n  }\n}\n.post-content-preview {\n  font-size: 13px;\n  font-style: italic;\n  color: #a3a3a3;\n}\n.post-content-preview:hover {\n  color: #0085a1;\n}\n@media only screen and (min-width: 768px) {\n  .post-content-preview {\n    font-size: 14px;\n  }\n}\n.section-heading {\n  font-size: 36px;\n  margin-top: 60px;\n  font-weight: 700;\n}\n.caption {\n  text-align: center;\n  font-size: 14px;\n  padding: 10px;\n  font-style: italic;\n  margin: 0;\n  display: block;\n  border-bottom-right-radius: 5px;\n  border-bottom-left-radius: 5px;\n}\nfooter {\n  font-size: 20px;\n  padding: 50px 0 65px;\n}\nfooter .list-inline {\n  margin: 0;\n  padding: 0;\n}\nfooter .copyright {\n  font-size: 14px;\n  text-align: center;\n  margin-bottom: 0;\n}\nfooter .copyright a {\n  color: #337ab7;\n}\nfooter .copyright a:hover,\nfooter .copyright a:focus {\n  color: #0085a1;\n}\n.floating-label-form-group {\n  font-size: 14px;\n  position: relative;\n  margin-bottom: 0;\n  padding-bottom: 0.5em;\n  border-bottom: 1px solid #eeeeee;\n}\n.floating-label-form-group input,\n.floating-label-form-group textarea {\n  z-index: 1;\n  position: relative;\n  padding-right: 0;\n  padding-left: 0;\n  border: none;\n  border-radius: 0;\n  font-size: 1.5em;\n  background: none;\n  box-shadow: none !important;\n  resize: none;\n}\n.floating-label-form-group label {\n  display: block;\n  z-index: 0;\n  position: relative;\n  top: 2em;\n  margin: 0;\n  font-size: 0.85em;\n  line-height: 1.764705882em;\n  vertical-align: middle;\n  vertical-align: baseline;\n  opacity: 0;\n  -webkit-transition: top 0.3s ease, opacity 0.3s ease;\n  -moz-transition: top 0.3s ease, opacity 0.3s ease;\n  -ms-transition: top 0.3s ease, opacity 0.3s ease;\n  transition: top 0.3s ease, opacity 0.3s ease;\n}\n.floating-label-form-group::not(:first-child) {\n  padding-left: 14px;\n  border-left: 1px solid #eeeeee;\n}\n.floating-label-form-group-with-value label {\n  top: 0;\n  opacity: 1;\n}\n.floating-label-form-group-with-focus label {\n  color: #0085a1;\n}\nform .row:first-child .floating-label-form-group {\n  border-top: 1px solid #eeeeee;\n}\n.btn {\n  /* Hux learn from\n     *     TypeIsBeautiful,\n     *     [This Post](http://zhuanlan.zhihu.com/ibuick/20186806) etc.\n     */\n  font-family: -apple-system, BlinkMacSystemFont, \"Helvetica Neue\", \"Arial\", \"PingFang SC\", \"Hiragino Sans GB\", \"STHeiti\", \"Microsoft YaHei\", \"Microsoft JhengHei\", \"Source Han Sans SC\", \"Noto Sans CJK SC\", \"Source Han Sans CN\", \"Noto Sans SC\", \"Source Han Sans TC\", \"Noto Sans CJK TC\", \"WenQuanYi Micro Hei\", SimSun, sans-serif;\n  line-height: 1.7;\n  text-transform: uppercase;\n  font-size: 14px;\n  font-weight: 800;\n  letter-spacing: 1px;\n  border-radius: 0;\n  padding: 15px 25px;\n}\n.btn-lg {\n  font-size: 16px;\n  padding: 25px 35px;\n}\n.btn-default:hover,\n.btn-default:focus {\n  background-color: #0085a1;\n  border: 1px solid #0085a1;\n  color: white;\n}\n.pager {\n  margin: 20px 0 0 !important;\n  padding: 0px !important;\n}\n.pager li > a,\n.pager li > span {\n  /* Hux learn from\n     *     TypeIsBeautiful,\n     *     [This Post](http://zhuanlan.zhihu.com/ibuick/20186806) etc.\n     */\n  font-family: -apple-system, BlinkMacSystemFont, \"Helvetica Neue\", \"Arial\", \"PingFang SC\", \"Hiragino Sans GB\", \"STHeiti\", \"Microsoft YaHei\", \"Microsoft JhengHei\", \"Source Han Sans SC\", \"Noto Sans CJK SC\", \"Source Han Sans CN\", \"Noto Sans SC\", \"Source Han Sans TC\", \"Noto Sans CJK TC\", \"WenQuanYi Micro Hei\", SimSun, sans-serif;\n  line-height: 1.7;\n  text-transform: uppercase;\n  font-size: 13px;\n  font-weight: 800;\n  letter-spacing: 1px;\n  padding: 10px;\n  background-color: white;\n  border-radius: 0;\n}\n@media only screen and (min-width: 768px) {\n  .pager li > a,\n  .pager li > span {\n    font-size: 14px;\n    padding: 15px 25px;\n  }\n}\n.pager li > a {\n  color: #404040;\n}\n.pager li > a:hover,\n.pager li > a:focus {\n  color: white;\n  background-color: #0085a1;\n  border: 1px solid #0085a1;\n}\n.pager li > a:hover > span,\n.pager li > a:focus > span {\n  color: white;\n}\n.pager .disabled > a,\n.pager .disabled > a:hover,\n.pager .disabled > a:focus,\n.pager .disabled > span {\n  color: #cccccc;\n  background-color: #404040;\n  cursor: not-allowed;\n}\n::-moz-selection {\n  color: white;\n  text-shadow: none;\n  background: #0085a1;\n}\n::selection {\n  color: white;\n  text-shadow: none;\n  background: #0085a1;\n}\nimg::selection {\n  color: white;\n  background: transparent;\n}\nimg::-moz-selection {\n  color: white;\n  background: transparent;\n}\n/* Hux add tags support */\n.d-none {\n  display: none !important;\n}\n.tags {\n  margin-bottom: -5px;\n}\n.tags.tags-sup a,\n.tags.tags-sup .tag {\n  padding: 0 10px 0 12px;\n}\n.tags a,\n.tags .tag {\n  display: inline-block;\n  border: 1px solid rgba(255, 255, 255, 0.8);\n  border-radius: 999em;\n  padding: 0 10px 0 10px;\n  color: #ffffff;\n  line-height: 24px;\n  font-size: 12px;\n  text-decoration: none;\n  margin: 0 1px;\n  margin-bottom: 6px;\n  cursor: pointer;\n}\n.tags a > sup,\n.tags .tag > sup {\n  margin-left: -2px;\n  font-weight: 700;\n}\n.tags a:hover,\n.tags .tag:hover,\n.tags a:active,\n.tags .tag:active {\n  color: white;\n  border-color: white;\n  background-color: rgba(255, 255, 255, 0.4);\n  text-decoration: none;\n}\n@media only screen and (min-width: 768px) {\n  .tags a,\n  .tags .tag {\n    margin-right: 5px;\n  }\n}\n#tag-heading {\n  padding: 70px 0 60px;\n}\n@media only screen and (min-width: 768px) {\n  #tag-heading {\n    padding: 55px 0;\n  }\n}\n#tag_cloud {\n  margin: 20px 0 15px 0;\n}\n#tag_cloud a,\n#tag_cloud .tag {\n  transition-property: all;\n  transition-duration: 0.4s;\n  transition-timing-function: ease;\n  font-size: 14px;\n  border: none;\n  line-height: 28px;\n  margin: 0 2px;\n  margin-bottom: 8px;\n  background: #f3f5f5;\n}\n#tag_cloud a:hover,\n#tag_cloud .tag:hover,\n#tag_cloud a:active,\n#tag_cloud .tag:active,\n#tag_cloud a.focus,\n#tag_cloud .tag.focus {\n  background-color: #0085a1 !important;\n}\n#tag_cloud a.focus,\n#tag_cloud .tag.focus {\n  box-shadow: rgba(0, 0, 0, 0.117647) 0 1px 6px, rgba(0, 0, 0, 0.239216) 0 1px 4px;\n}\n#tag_cloud a.tag-button--all,\n#tag_cloud .tag.tag-button--all {\n  font-weight: 700;\n  color: #0085a1!important;\n}\n#tag_cloud a.tag-button--all:hover,\n#tag_cloud .tag.tag-button--all:hover,\n#tag_cloud a.tag-button--all:active,\n#tag_cloud .tag.tag-button--all:active,\n#tag_cloud a.tag-button--all.focus,\n#tag_cloud .tag.tag-button--all.focus {\n  background-color: #e4e4e4 !important;\n}\n@media only screen and (min-width: 768px) {\n  #tag_cloud {\n    margin-bottom: 25px;\n  }\n}\n.tag-comments {\n  font-size: 12px;\n}\n@media only screen and (min-width: 768px) {\n  .tag-comments {\n    font-size: 14px;\n  }\n}\n.t:first-child {\n  margin-top: 0px;\n}\n.listing-seperator {\n  color: #0085a1;\n  font-size: 21px !important;\n}\n.listing-seperator::before {\n  margin-right: 5px;\n}\n@media only screen and (min-width: 768px) {\n  .listing-seperator {\n    font-size: 20px !important;\n    line-height: 2 !important;\n  }\n}\n.mini-post-list {\n  margin: 20px 0 15px 0;\n}\n.mini-post-list .tag-text {\n  font-weight: 200;\n  /* Hux learn from\n     *     TypeIsBeautiful,\n     *     [This Post](http://zhuanlan.zhihu.com/ibuick/20186806) etc.\n     */\n  font-family: -apple-system, BlinkMacSystemFont, \"Helvetica Neue\", \"Arial\", \"PingFang SC\", \"Hiragino Sans GB\", \"STHeiti\", \"Microsoft YaHei\", \"Microsoft JhengHei\", \"Source Han Sans SC\", \"Noto Sans CJK SC\", \"Source Han Sans CN\", \"Noto Sans SC\", \"Source Han Sans TC\", \"Noto Sans CJK TC\", \"WenQuanYi Micro Hei\", SimSun, sans-serif;\n  line-height: 1.7;\n}\n.mini-post-list .post-preview {\n  position: relative;\n}\n.mini-post-list .post-preview > a .post-title {\n  font-size: 16px;\n  font-weight: 500;\n  margin-top: 20px;\n}\n.mini-post-list .post-preview > a .post-subtitle {\n  font-size: 13px;\n}\n.mini-post-list .post-preview > .post-meta {\n  position: absolute;\n  right: 5px;\n  bottom: 0px;\n  margin: 0px;\n  font-size: 12px;\n  line-height: 12px;\n}\n@media only screen and (min-width: 768px) {\n  .mini-post-list .post-preview {\n    margin-left: 20px;\n  }\n  .mini-post-list .post-preview > a > .post-title {\n    font-size: 18px;\n    line-height: 1.3;\n  }\n  .mini-post-list .post-preview > a > .post-subtitle {\n    font-size: 14px;\n  }\n  .mini-post-list .post-preview .post-meta {\n    font-size: 18px;\n  }\n}\n/* Tags support End*/\n/* Hux make all img responsible in post-container */\n.post-container img {\n  display: block;\n  max-width: 100%;\n  height: auto;\n  margin: 1.5em auto 1.6em auto;\n}\n/* Hux Optimize UserExperience */\n.navbar-default .navbar-toggle:focus,\n.navbar-default .navbar-toggle:hover {\n  background-color: inherit;\n}\n.navbar-default .navbar-toggle:active {\n  background-color: rgba(255, 255, 255, 0.25);\n}\n/* Hux customize Style for navBar button */\n.navbar-default .navbar-toggle {\n  border-color: transparent;\n  padding: 19px 16px;\n  margin-top: 2px;\n  margin-right: 2px;\n  margin-bottom: 2px;\n  border-radius: 50%;\n}\n.navbar-default .navbar-toggle .icon-bar {\n  width: 18px;\n  border-radius: 0px;\n  background-color: white;\n}\n.navbar-default .navbar-toggle .icon-bar + .icon-bar {\n  margin-top: 3px;\n}\n/* Hux customize Style for Duoshuo */\n.comment {\n  margin-top: 20px;\n}\n.comment #ds-thread #ds-reset a.ds-like-thread-button {\n  border: 1px solid #ddd;\n  border-radius: 0px;\n  background: white;\n  box-shadow: none;\n  text-shadow: none;\n}\n.comment #ds-thread #ds-reset li.ds-tab a.ds-current {\n  border: 1px solid #ddd;\n  border-radius: 0px;\n  background: white;\n  box-shadow: none;\n  text-shadow: none;\n}\n.comment #ds-thread #ds-reset .ds-textarea-wrapper {\n  background: none;\n}\n.comment #ds-thread #ds-reset .ds-gradient-bg {\n  background: none;\n}\n.comment #ds-thread #ds-reset .ds-post-options {\n  border-bottom: 1px solid #ccc;\n}\n.comment #ds-thread #ds-reset .ds-post-button {\n  border-bottom: 1px solid #ccc;\n}\n.comment #ds-thread #ds-reset .ds-post-button {\n  background: white;\n  box-shadow: none;\n}\n.comment #ds-thread #ds-reset .ds-post-button:hover {\n  background: #eeeeee;\n}\n#ds-smilies-tooltip ul.ds-smilies-tabs li a {\n  background: white !important;\n}\n.page-fullscreen .intro-header {\n  position: fixed;\n  left: 0;\n  top: 0;\n  width: 100%;\n  height: 100%;\n}\n.page-fullscreen #tag-heading {\n  position: fixed;\n  left: 0;\n  top: 0;\n  padding-bottom: 150px;\n  width: 100%;\n  height: 100%;\n  display: -webkit-box;\n  -webkit-box-orient: vertical;\n  -webkit-box-pack: center;\n  -webkit-box-align: center;\n  display: -webkit-flex;\n  -webkit-align-items: center;\n  -webkit-justify-content: center;\n  -webkit-flex-direction: column;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  flex-direction: column;\n}\n.page-fullscreen footer {\n  position: absolute;\n  width: 100%;\n  bottom: 0;\n  padding-bottom: 20px;\n  opacity: 0.6;\n  color: #fff;\n}\n.page-fullscreen footer .copyright {\n  color: #fff;\n}\n.page-fullscreen footer .copyright a {\n  color: #fff;\n}\n.page-fullscreen footer .copyright a:hover {\n  color: #ddd;\n}\n.MathJax_SVG_Display {\n  overflow: auto;\n  -webkit-overflow-scrolling: touch;\n}\n"
  },
  {
    "path": "feed.xml",
    "content": "---\nlayout: null\n---\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\">\n  <channel>\n    <title>{{ site.title | xml_escape }}</title>\n    <description>{{ site.description | xml_escape }}</description>\n    <link>{{ site.url }}{{ site.baseurl }}/</link>\n    <atom:link href=\"{{ \"/feed.xml\" | prepend: site.baseurl | prepend: site.url }}\" rel=\"self\" type=\"application/rss+xml\" />\n    <pubDate>{{ site.time | date_to_rfc822 }}</pubDate>\n    <lastBuildDate>{{ site.time | date_to_rfc822 }}</lastBuildDate>\n    <generator>Jekyll v{{ jekyll.version }}</generator>\n    {% for post in site.posts limit:10 %}\n      <item>\n        <title>{{ post.title | xml_escape }}</title>\n        <description>{{ post.content | xml_escape }}</description>\n        <pubDate>{{ post.date | date_to_rfc822 }}</pubDate>\n        <link>{{ post.url | prepend: site.baseurl | prepend: site.url }}</link>\n        <guid isPermaLink=\"true\">{{ post.url | prepend: site.baseurl | prepend: site.url }}</guid>\n        {% for tag in post.tags %}\n        <category>{{ tag | xml_escape }}</category>\n        {% endfor %}\n        {% for cat in post.categories %}\n        <category>{{ cat | xml_escape }}</category>\n        {% endfor %}\n      </item>\n    {% endfor %}\n  </channel>\n</rss>\n"
  },
  {
    "path": "index.html",
    "content": "---\nlayout: page\ntitle: 小马哥的技术博客\nauthor: mercyblitz\ndescription: \"这里将深入探讨相关技术，包括行业动态，架构设计，设计模式，框架使用，源码分析等。 :)\"\n---\n\n{% for post in paginator.posts %}\n<div class=\"post-preview\">\n    <a href=\"{{ post.url | prepend: site.baseurl }}\">\n        <h2 class=\"post-title\">\n            {{ post.title }}\n        </h2>\n        {% if post.subtitle %}\n        <h3 class=\"post-subtitle\">\n            {{ post.subtitle }}\n        </h3>\n        {% endif %}\n        <div class=\"post-content-preview\">\n            {% if post.lang == 'en' %}\n                {{ post.content | strip_html | truncate:300 }}\n            {% else %}\n                {{ post.content | strip_html | truncate:200 }}\n            {% endif %}\n        </div>\n    </a>\n    <p class=\"post-meta\">\n        Posted by {% if post.author %}{{ post.author }}{% else %}{{ site.title }}{% endif %} on {{ post.date | date: \"%B %-d, %Y\" }}\n    </p>\n</div>\n<hr>\n{% endfor %}\n\n<!-- Pager -->\n{% if paginator.total_pages > 1 %}\n<ul class=\"pager\">\n    {% if paginator.previous_page %}\n    <li class=\"previous\">\n        <a href=\"{{ paginator.previous_page_path | prepend: site.baseurl | replace: '//', '/' }}\">&larr; Newer Posts</a>\n    </li>\n    {% endif %}\n    {% if paginator.next_page %}\n    <li class=\"next\">\n        <a href=\"{{ paginator.next_page_path | prepend: site.baseurl | replace: '//', '/' }}\">Older Posts &rarr;</a>\n    </li>\n    {% endif %}\n</ul>\n{% endif %}\n"
  },
  {
    "path": "js/archive.js",
    "content": "/*\nCredits: this script is shamelessly borrowed from\nhttps://github.com/kitian616/jekyll-TeXt-theme\n*/\n(function() {\n  function queryString() {\n    // This function is anonymous, is executed immediately and\n    // the return value is assigned to QueryString!\n    var i = 0, queryObj = {}, pair;\n    var queryStr = window.location.search.substring(1);\n    var queryArr = queryStr.split('&');\n    for (i = 0; i < queryArr.length; i++) {\n      pair = queryArr[i].split('=');\n      // If first entry with this name\n      if (typeof queryObj[pair[0]] === 'undefined') {\n        queryObj[pair[0]] = pair[1];\n        // If second entry with this name\n      } else if (typeof queryObj[pair[0]] === 'string') {\n        queryObj[pair[0]] = [queryObj[pair[0]], pair[1]];\n        // If third or later entry with this name\n      } else {\n        queryObj[pair[0]].push(pair[1]);\n      }\n    }\n    return queryObj;\n  }\n\n  var setUrlQuery = (function() {\n    var baseUrl =  window.location.href.split('?')[0];\n    return function(query) {\n      if (typeof query === 'string') {\n        window.history.replaceState(null, '', baseUrl + query);\n      } else {\n        window.history.replaceState(null, '', baseUrl);\n      }\n    };\n  })();\n\n  $(document).ready(function() {\n    var $tags = $('.js-tags');\n    var $articleTags = $tags.find('.tag-button');\n    var $tagShowAll = $tags.find('.tag-button--all');\n    var $result = $('.js-result');\n    var $sections = $result.find('section');\n    var sectionArticles = []\n    var $lastFocusButton = null;\n    var sectionTopArticleIndex = [];\n    var hasInit = false;\n\n    $sections.each(function() {\n      sectionArticles.push($(this).find('.item'));\n    });\n\n    function init() {\n      var i, index = 0;\n      for (i = 0; i < $sections.length; i++) {\n        sectionTopArticleIndex.push(index);\n        index += $sections.eq(i).find('.item').length;\n      }\n      sectionTopArticleIndex.push(index);\n    }\n\n    function searchButtonsByTag(_tag/*raw tag*/) {\n      if (!_tag) {\n        return $tagShowAll;\n      }\n      var _buttons = $articleTags.filter('[data-encode=\"' + _tag + '\"]');\n      if (_buttons.length === 0) {\n        return $tagShowAll;\n      }\n      return _buttons;\n    }\n\n    function buttonFocus(target) {\n      if (target) {\n        target.addClass('focus');\n        $lastFocusButton && !$lastFocusButton.is(target) && $lastFocusButton.removeClass('focus');\n        $lastFocusButton = target;\n      }\n    }\n\n    function tagSelect (tag/*raw tag*/, target) {\n      var result = {}, $articles;\n      var i, j, k, _tag;\n\n      for (i = 0; i < sectionArticles.length; i++) {\n        $articles = sectionArticles[i];\n        for (j = 0; j < $articles.length; j++) {\n          if (tag === '' || tag === undefined) {\n            result[i] || (result[i] = {});\n            result[i][j] = true;\n          } else {\n            var tags = $articles.eq(j).data('tags').split(',');\n            for (k = 0; k < tags.length; k++) {\n              if (tags[k] === tag) {\n                result[i] || (result[i] = {});\n                result[i][j] = true; break;\n              }\n            }\n          }\n        }\n      }\n\n      for (i = 0; i < sectionArticles.length; i++) {\n        result[i] && $sections.eq(i).removeClass('d-none');\n        result[i] || $sections.eq(i).addClass('d-none');\n        for (j = 0; j < sectionArticles[i].length; j++) {\n          if (result[i] && result[i][j]) {\n            sectionArticles[i].eq(j).removeClass('d-none');\n          } else {\n            sectionArticles[i].eq(j).addClass('d-none');\n          }\n        }\n      }\n\n      hasInit || ($result.removeClass('d-none'), hasInit = true);\n\n\n      if (target) {\n        buttonFocus(target);\n        _tag = target.attr('data-encode');\n        if (_tag === '' || typeof _tag !== 'string') {\n          setUrlQuery();\n        } else {\n          setUrlQuery('?tag=' + _tag);\n        }\n      } else {\n        buttonFocus(searchButtonsByTag(tag));\n      }\n    }\n\n    var query = queryString(), \n        _tag = query.tag;\n\n    init(); \n    tagSelect(_tag);\n\n    $tags.on('click', 'a', function() {   /* only change */\n      tagSelect($(this).data('encode'), $(this));\n    });\n\n  });\n})();\n"
  },
  {
    "path": "js/bootstrap.js",
    "content": "/*!\n * Bootstrap v3.3.2 (http://getbootstrap.com)\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n\nif (typeof jQuery === 'undefined') {\n  throw new Error('Bootstrap\\'s JavaScript requires jQuery')\n}\n\n+function ($) {\n  'use strict';\n  var version = $.fn.jquery.split(' ')[0].split('.')\n  if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1)) {\n    throw new Error('Bootstrap\\'s JavaScript requires jQuery version 1.9.1 or higher')\n  }\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: transition.js v3.3.2\n * http://getbootstrap.com/javascript/#transitions\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)\n  // ============================================================\n\n  function transitionEnd() {\n    var el = document.createElement('bootstrap')\n\n    var transEndEventNames = {\n      WebkitTransition : 'webkitTransitionEnd',\n      MozTransition    : 'transitionend',\n      OTransition      : 'oTransitionEnd otransitionend',\n      transition       : 'transitionend'\n    }\n\n    for (var name in transEndEventNames) {\n      if (el.style[name] !== undefined) {\n        return { end: transEndEventNames[name] }\n      }\n    }\n\n    return false // explicit for ie8 (  ._.)\n  }\n\n  // http://blog.alexmaccaw.com/css-transitions\n  $.fn.emulateTransitionEnd = function (duration) {\n    var called = false\n    var $el = this\n    $(this).one('bsTransitionEnd', function () { called = true })\n    var callback = function () { if (!called) $($el).trigger($.support.transition.end) }\n    setTimeout(callback, duration)\n    return this\n  }\n\n  $(function () {\n    $.support.transition = transitionEnd()\n\n    if (!$.support.transition) return\n\n    $.event.special.bsTransitionEnd = {\n      bindType: $.support.transition.end,\n      delegateType: $.support.transition.end,\n      handle: function (e) {\n        if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)\n      }\n    }\n  })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: alert.js v3.3.2\n * http://getbootstrap.com/javascript/#alerts\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // ALERT CLASS DEFINITION\n  // ======================\n\n  var dismiss = '[data-dismiss=\"alert\"]'\n  var Alert   = function (el) {\n    $(el).on('click', dismiss, this.close)\n  }\n\n  Alert.VERSION = '3.3.2'\n\n  Alert.TRANSITION_DURATION = 150\n\n  Alert.prototype.close = function (e) {\n    var $this    = $(this)\n    var selector = $this.attr('data-target')\n\n    if (!selector) {\n      selector = $this.attr('href')\n      selector = selector && selector.replace(/.*(?=#[^\\s]*$)/, '') // strip for ie7\n    }\n\n    var $parent = $(selector)\n\n    if (e) e.preventDefault()\n\n    if (!$parent.length) {\n      $parent = $this.closest('.alert')\n    }\n\n    $parent.trigger(e = $.Event('close.bs.alert'))\n\n    if (e.isDefaultPrevented()) return\n\n    $parent.removeClass('in')\n\n    function removeElement() {\n      // detach from parent, fire event then clean up data\n      $parent.detach().trigger('closed.bs.alert').remove()\n    }\n\n    $.support.transition && $parent.hasClass('fade') ?\n      $parent\n        .one('bsTransitionEnd', removeElement)\n        .emulateTransitionEnd(Alert.TRANSITION_DURATION) :\n      removeElement()\n  }\n\n\n  // ALERT PLUGIN DEFINITION\n  // =======================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this = $(this)\n      var data  = $this.data('bs.alert')\n\n      if (!data) $this.data('bs.alert', (data = new Alert(this)))\n      if (typeof option == 'string') data[option].call($this)\n    })\n  }\n\n  var old = $.fn.alert\n\n  $.fn.alert             = Plugin\n  $.fn.alert.Constructor = Alert\n\n\n  // ALERT NO CONFLICT\n  // =================\n\n  $.fn.alert.noConflict = function () {\n    $.fn.alert = old\n    return this\n  }\n\n\n  // ALERT DATA-API\n  // ==============\n\n  $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: button.js v3.3.2\n * http://getbootstrap.com/javascript/#buttons\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // BUTTON PUBLIC CLASS DEFINITION\n  // ==============================\n\n  var Button = function (element, options) {\n    this.$element  = $(element)\n    this.options   = $.extend({}, Button.DEFAULTS, options)\n    this.isLoading = false\n  }\n\n  Button.VERSION  = '3.3.2'\n\n  Button.DEFAULTS = {\n    loadingText: 'loading...'\n  }\n\n  Button.prototype.setState = function (state) {\n    var d    = 'disabled'\n    var $el  = this.$element\n    var val  = $el.is('input') ? 'val' : 'html'\n    var data = $el.data()\n\n    state = state + 'Text'\n\n    if (data.resetText == null) $el.data('resetText', $el[val]())\n\n    // push to event loop to allow forms to submit\n    setTimeout($.proxy(function () {\n      $el[val](data[state] == null ? this.options[state] : data[state])\n\n      if (state == 'loadingText') {\n        this.isLoading = true\n        $el.addClass(d).attr(d, d)\n      } else if (this.isLoading) {\n        this.isLoading = false\n        $el.removeClass(d).removeAttr(d)\n      }\n    }, this), 0)\n  }\n\n  Button.prototype.toggle = function () {\n    var changed = true\n    var $parent = this.$element.closest('[data-toggle=\"buttons\"]')\n\n    if ($parent.length) {\n      var $input = this.$element.find('input')\n      if ($input.prop('type') == 'radio') {\n        if ($input.prop('checked') && this.$element.hasClass('active')) changed = false\n        else $parent.find('.active').removeClass('active')\n      }\n      if (changed) $input.prop('checked', !this.$element.hasClass('active')).trigger('change')\n    } else {\n      this.$element.attr('aria-pressed', !this.$element.hasClass('active'))\n    }\n\n    if (changed) this.$element.toggleClass('active')\n  }\n\n\n  // BUTTON PLUGIN DEFINITION\n  // ========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.button')\n      var options = typeof option == 'object' && option\n\n      if (!data) $this.data('bs.button', (data = new Button(this, options)))\n\n      if (option == 'toggle') data.toggle()\n      else if (option) data.setState(option)\n    })\n  }\n\n  var old = $.fn.button\n\n  $.fn.button             = Plugin\n  $.fn.button.Constructor = Button\n\n\n  // BUTTON NO CONFLICT\n  // ==================\n\n  $.fn.button.noConflict = function () {\n    $.fn.button = old\n    return this\n  }\n\n\n  // BUTTON DATA-API\n  // ===============\n\n  $(document)\n    .on('click.bs.button.data-api', '[data-toggle^=\"button\"]', function (e) {\n      var $btn = $(e.target)\n      if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')\n      Plugin.call($btn, 'toggle')\n      e.preventDefault()\n    })\n    .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^=\"button\"]', function (e) {\n      $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type))\n    })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: carousel.js v3.3.2\n * http://getbootstrap.com/javascript/#carousel\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // CAROUSEL CLASS DEFINITION\n  // =========================\n\n  var Carousel = function (element, options) {\n    this.$element    = $(element)\n    this.$indicators = this.$element.find('.carousel-indicators')\n    this.options     = options\n    this.paused      =\n    this.sliding     =\n    this.interval    =\n    this.$active     =\n    this.$items      = null\n\n    this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this))\n\n    this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element\n      .on('mouseenter.bs.carousel', $.proxy(this.pause, this))\n      .on('mouseleave.bs.carousel', $.proxy(this.cycle, this))\n  }\n\n  Carousel.VERSION  = '3.3.2'\n\n  Carousel.TRANSITION_DURATION = 600\n\n  Carousel.DEFAULTS = {\n    interval: 5000,\n    pause: 'hover',\n    wrap: true,\n    keyboard: true\n  }\n\n  Carousel.prototype.keydown = function (e) {\n    if (/input|textarea/i.test(e.target.tagName)) return\n    switch (e.which) {\n      case 37: this.prev(); break\n      case 39: this.next(); break\n      default: return\n    }\n\n    e.preventDefault()\n  }\n\n  Carousel.prototype.cycle = function (e) {\n    e || (this.paused = false)\n\n    this.interval && clearInterval(this.interval)\n\n    this.options.interval\n      && !this.paused\n      && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))\n\n    return this\n  }\n\n  Carousel.prototype.getItemIndex = function (item) {\n    this.$items = item.parent().children('.item')\n    return this.$items.index(item || this.$active)\n  }\n\n  Carousel.prototype.getItemForDirection = function (direction, active) {\n    var activeIndex = this.getItemIndex(active)\n    var willWrap = (direction == 'prev' && activeIndex === 0)\n                || (direction == 'next' && activeIndex == (this.$items.length - 1))\n    if (willWrap && !this.options.wrap) return active\n    var delta = direction == 'prev' ? -1 : 1\n    var itemIndex = (activeIndex + delta) % this.$items.length\n    return this.$items.eq(itemIndex)\n  }\n\n  Carousel.prototype.to = function (pos) {\n    var that        = this\n    var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active'))\n\n    if (pos > (this.$items.length - 1) || pos < 0) return\n\n    if (this.sliding)       return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, \"slid\"\n    if (activeIndex == pos) return this.pause().cycle()\n\n    return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos))\n  }\n\n  Carousel.prototype.pause = function (e) {\n    e || (this.paused = true)\n\n    if (this.$element.find('.next, .prev').length && $.support.transition) {\n      this.$element.trigger($.support.transition.end)\n      this.cycle(true)\n    }\n\n    this.interval = clearInterval(this.interval)\n\n    return this\n  }\n\n  Carousel.prototype.next = function () {\n    if (this.sliding) return\n    return this.slide('next')\n  }\n\n  Carousel.prototype.prev = function () {\n    if (this.sliding) return\n    return this.slide('prev')\n  }\n\n  Carousel.prototype.slide = function (type, next) {\n    var $active   = this.$element.find('.item.active')\n    var $next     = next || this.getItemForDirection(type, $active)\n    var isCycling = this.interval\n    var direction = type == 'next' ? 'left' : 'right'\n    var that      = this\n\n    if ($next.hasClass('active')) return (this.sliding = false)\n\n    var relatedTarget = $next[0]\n    var slideEvent = $.Event('slide.bs.carousel', {\n      relatedTarget: relatedTarget,\n      direction: direction\n    })\n    this.$element.trigger(slideEvent)\n    if (slideEvent.isDefaultPrevented()) return\n\n    this.sliding = true\n\n    isCycling && this.pause()\n\n    if (this.$indicators.length) {\n      this.$indicators.find('.active').removeClass('active')\n      var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)])\n      $nextIndicator && $nextIndicator.addClass('active')\n    }\n\n    var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, \"slid\"\n    if ($.support.transition && this.$element.hasClass('slide')) {\n      $next.addClass(type)\n      $next[0].offsetWidth // force reflow\n      $active.addClass(direction)\n      $next.addClass(direction)\n      $active\n        .one('bsTransitionEnd', function () {\n          $next.removeClass([type, direction].join(' ')).addClass('active')\n          $active.removeClass(['active', direction].join(' '))\n          that.sliding = false\n          setTimeout(function () {\n            that.$element.trigger(slidEvent)\n          }, 0)\n        })\n        .emulateTransitionEnd(Carousel.TRANSITION_DURATION)\n    } else {\n      $active.removeClass('active')\n      $next.addClass('active')\n      this.sliding = false\n      this.$element.trigger(slidEvent)\n    }\n\n    isCycling && this.cycle()\n\n    return this\n  }\n\n\n  // CAROUSEL PLUGIN DEFINITION\n  // ==========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.carousel')\n      var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)\n      var action  = typeof option == 'string' ? option : options.slide\n\n      if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))\n      if (typeof option == 'number') data.to(option)\n      else if (action) data[action]()\n      else if (options.interval) data.pause().cycle()\n    })\n  }\n\n  var old = $.fn.carousel\n\n  $.fn.carousel             = Plugin\n  $.fn.carousel.Constructor = Carousel\n\n\n  // CAROUSEL NO CONFLICT\n  // ====================\n\n  $.fn.carousel.noConflict = function () {\n    $.fn.carousel = old\n    return this\n  }\n\n\n  // CAROUSEL DATA-API\n  // =================\n\n  var clickHandler = function (e) {\n    var href\n    var $this   = $(this)\n    var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\\s]+$)/, '')) // strip for ie7\n    if (!$target.hasClass('carousel')) return\n    var options = $.extend({}, $target.data(), $this.data())\n    var slideIndex = $this.attr('data-slide-to')\n    if (slideIndex) options.interval = false\n\n    Plugin.call($target, options)\n\n    if (slideIndex) {\n      $target.data('bs.carousel').to(slideIndex)\n    }\n\n    e.preventDefault()\n  }\n\n  $(document)\n    .on('click.bs.carousel.data-api', '[data-slide]', clickHandler)\n    .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler)\n\n  $(window).on('load', function () {\n    $('[data-ride=\"carousel\"]').each(function () {\n      var $carousel = $(this)\n      Plugin.call($carousel, $carousel.data())\n    })\n  })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: collapse.js v3.3.2\n * http://getbootstrap.com/javascript/#collapse\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // COLLAPSE PUBLIC CLASS DEFINITION\n  // ================================\n\n  var Collapse = function (element, options) {\n    this.$element      = $(element)\n    this.options       = $.extend({}, Collapse.DEFAULTS, options)\n    this.$trigger      = $(this.options.trigger).filter('[href=\"#' + element.id + '\"], [data-target=\"#' + element.id + '\"]')\n    this.transitioning = null\n\n    if (this.options.parent) {\n      this.$parent = this.getParent()\n    } else {\n      this.addAriaAndCollapsedClass(this.$element, this.$trigger)\n    }\n\n    if (this.options.toggle) this.toggle()\n  }\n\n  Collapse.VERSION  = '3.3.2'\n\n  Collapse.TRANSITION_DURATION = 350\n\n  Collapse.DEFAULTS = {\n    toggle: true,\n    trigger: '[data-toggle=\"collapse\"]'\n  }\n\n  Collapse.prototype.dimension = function () {\n    var hasWidth = this.$element.hasClass('width')\n    return hasWidth ? 'width' : 'height'\n  }\n\n  Collapse.prototype.show = function () {\n    if (this.transitioning || this.$element.hasClass('in')) return\n\n    var activesData\n    var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing')\n\n    if (actives && actives.length) {\n      activesData = actives.data('bs.collapse')\n      if (activesData && activesData.transitioning) return\n    }\n\n    var startEvent = $.Event('show.bs.collapse')\n    this.$element.trigger(startEvent)\n    if (startEvent.isDefaultPrevented()) return\n\n    if (actives && actives.length) {\n      Plugin.call(actives, 'hide')\n      activesData || actives.data('bs.collapse', null)\n    }\n\n    var dimension = this.dimension()\n\n    this.$element\n      .removeClass('collapse')\n      .addClass('collapsing')[dimension](0)\n      .attr('aria-expanded', true)\n\n    this.$trigger\n      .removeClass('collapsed')\n      .attr('aria-expanded', true)\n\n    this.transitioning = 1\n\n    var complete = function () {\n      this.$element\n        .removeClass('collapsing')\n        .addClass('collapse in')[dimension]('')\n      this.transitioning = 0\n      this.$element\n        .trigger('shown.bs.collapse')\n    }\n\n    if (!$.support.transition) return complete.call(this)\n\n    var scrollSize = $.camelCase(['scroll', dimension].join('-'))\n\n    this.$element\n      .one('bsTransitionEnd', $.proxy(complete, this))\n      .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize])\n  }\n\n  Collapse.prototype.hide = function () {\n    if (this.transitioning || !this.$element.hasClass('in')) return\n\n    var startEvent = $.Event('hide.bs.collapse')\n    this.$element.trigger(startEvent)\n    if (startEvent.isDefaultPrevented()) return\n\n    var dimension = this.dimension()\n\n    this.$element[dimension](this.$element[dimension]())[0].offsetHeight\n\n    this.$element\n      .addClass('collapsing')\n      .removeClass('collapse in')\n      .attr('aria-expanded', false)\n\n    this.$trigger\n      .addClass('collapsed')\n      .attr('aria-expanded', false)\n\n    this.transitioning = 1\n\n    var complete = function () {\n      this.transitioning = 0\n      this.$element\n        .removeClass('collapsing')\n        .addClass('collapse')\n        .trigger('hidden.bs.collapse')\n    }\n\n    if (!$.support.transition) return complete.call(this)\n\n    this.$element\n      [dimension](0)\n      .one('bsTransitionEnd', $.proxy(complete, this))\n      .emulateTransitionEnd(Collapse.TRANSITION_DURATION)\n  }\n\n  Collapse.prototype.toggle = function () {\n    this[this.$element.hasClass('in') ? 'hide' : 'show']()\n  }\n\n  Collapse.prototype.getParent = function () {\n    return $(this.options.parent)\n      .find('[data-toggle=\"collapse\"][data-parent=\"' + this.options.parent + '\"]')\n      .each($.proxy(function (i, element) {\n        var $element = $(element)\n        this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element)\n      }, this))\n      .end()\n  }\n\n  Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {\n    var isOpen = $element.hasClass('in')\n\n    $element.attr('aria-expanded', isOpen)\n    $trigger\n      .toggleClass('collapsed', !isOpen)\n      .attr('aria-expanded', isOpen)\n  }\n\n  function getTargetFromTrigger($trigger) {\n    var href\n    var target = $trigger.attr('data-target')\n      || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\\s]+$)/, '') // strip for ie7\n\n    return $(target)\n  }\n\n\n  // COLLAPSE PLUGIN DEFINITION\n  // ==========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.collapse')\n      var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)\n\n      if (!data && options.toggle && option == 'show') options.toggle = false\n      if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.collapse\n\n  $.fn.collapse             = Plugin\n  $.fn.collapse.Constructor = Collapse\n\n\n  // COLLAPSE NO CONFLICT\n  // ====================\n\n  $.fn.collapse.noConflict = function () {\n    $.fn.collapse = old\n    return this\n  }\n\n\n  // COLLAPSE DATA-API\n  // =================\n\n  $(document).on('click.bs.collapse.data-api', '[data-toggle=\"collapse\"]', function (e) {\n    var $this   = $(this)\n\n    if (!$this.attr('data-target')) e.preventDefault()\n\n    var $target = getTargetFromTrigger($this)\n    var data    = $target.data('bs.collapse')\n    var option  = data ? 'toggle' : $.extend({}, $this.data(), { trigger: this })\n\n    Plugin.call($target, option)\n  })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: dropdown.js v3.3.2\n * http://getbootstrap.com/javascript/#dropdowns\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // DROPDOWN CLASS DEFINITION\n  // =========================\n\n  var backdrop = '.dropdown-backdrop'\n  var toggle   = '[data-toggle=\"dropdown\"]'\n  var Dropdown = function (element) {\n    $(element).on('click.bs.dropdown', this.toggle)\n  }\n\n  Dropdown.VERSION = '3.3.2'\n\n  Dropdown.prototype.toggle = function (e) {\n    var $this = $(this)\n\n    if ($this.is('.disabled, :disabled')) return\n\n    var $parent  = getParent($this)\n    var isActive = $parent.hasClass('open')\n\n    clearMenus()\n\n    if (!isActive) {\n      if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {\n        // if mobile we use a backdrop because click events don't delegate\n        $('<div class=\"dropdown-backdrop\"/>').insertAfter($(this)).on('click', clearMenus)\n      }\n\n      var relatedTarget = { relatedTarget: this }\n      $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))\n\n      if (e.isDefaultPrevented()) return\n\n      $this\n        .trigger('focus')\n        .attr('aria-expanded', 'true')\n\n      $parent\n        .toggleClass('open')\n        .trigger('shown.bs.dropdown', relatedTarget)\n    }\n\n    return false\n  }\n\n  Dropdown.prototype.keydown = function (e) {\n    if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return\n\n    var $this = $(this)\n\n    e.preventDefault()\n    e.stopPropagation()\n\n    if ($this.is('.disabled, :disabled')) return\n\n    var $parent  = getParent($this)\n    var isActive = $parent.hasClass('open')\n\n    if ((!isActive && e.which != 27) || (isActive && e.which == 27)) {\n      if (e.which == 27) $parent.find(toggle).trigger('focus')\n      return $this.trigger('click')\n    }\n\n    var desc = ' li:not(.divider):visible a'\n    var $items = $parent.find('[role=\"menu\"]' + desc + ', [role=\"listbox\"]' + desc)\n\n    if (!$items.length) return\n\n    var index = $items.index(e.target)\n\n    if (e.which == 38 && index > 0)                 index--                        // up\n    if (e.which == 40 && index < $items.length - 1) index++                        // down\n    if (!~index)                                      index = 0\n\n    $items.eq(index).trigger('focus')\n  }\n\n  function clearMenus(e) {\n    if (e && e.which === 3) return\n    $(backdrop).remove()\n    $(toggle).each(function () {\n      var $this         = $(this)\n      var $parent       = getParent($this)\n      var relatedTarget = { relatedTarget: this }\n\n      if (!$parent.hasClass('open')) return\n\n      $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))\n\n      if (e.isDefaultPrevented()) return\n\n      $this.attr('aria-expanded', 'false')\n      $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget)\n    })\n  }\n\n  function getParent($this) {\n    var selector = $this.attr('data-target')\n\n    if (!selector) {\n      selector = $this.attr('href')\n      selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\\s]*$)/, '') // strip for ie7\n    }\n\n    var $parent = selector && $(selector)\n\n    return $parent && $parent.length ? $parent : $this.parent()\n  }\n\n\n  // DROPDOWN PLUGIN DEFINITION\n  // ==========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this = $(this)\n      var data  = $this.data('bs.dropdown')\n\n      if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))\n      if (typeof option == 'string') data[option].call($this)\n    })\n  }\n\n  var old = $.fn.dropdown\n\n  $.fn.dropdown             = Plugin\n  $.fn.dropdown.Constructor = Dropdown\n\n\n  // DROPDOWN NO CONFLICT\n  // ====================\n\n  $.fn.dropdown.noConflict = function () {\n    $.fn.dropdown = old\n    return this\n  }\n\n\n  // APPLY TO STANDARD DROPDOWN ELEMENTS\n  // ===================================\n\n  $(document)\n    .on('click.bs.dropdown.data-api', clearMenus)\n    .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })\n    .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)\n    .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)\n    .on('keydown.bs.dropdown.data-api', '[role=\"menu\"]', Dropdown.prototype.keydown)\n    .on('keydown.bs.dropdown.data-api', '[role=\"listbox\"]', Dropdown.prototype.keydown)\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: modal.js v3.3.2\n * http://getbootstrap.com/javascript/#modals\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // MODAL CLASS DEFINITION\n  // ======================\n\n  var Modal = function (element, options) {\n    this.options        = options\n    this.$body          = $(document.body)\n    this.$element       = $(element)\n    this.$backdrop      =\n    this.isShown        = null\n    this.scrollbarWidth = 0\n\n    if (this.options.remote) {\n      this.$element\n        .find('.modal-content')\n        .load(this.options.remote, $.proxy(function () {\n          this.$element.trigger('loaded.bs.modal')\n        }, this))\n    }\n  }\n\n  Modal.VERSION  = '3.3.2'\n\n  Modal.TRANSITION_DURATION = 300\n  Modal.BACKDROP_TRANSITION_DURATION = 150\n\n  Modal.DEFAULTS = {\n    backdrop: true,\n    keyboard: true,\n    show: true\n  }\n\n  Modal.prototype.toggle = function (_relatedTarget) {\n    return this.isShown ? this.hide() : this.show(_relatedTarget)\n  }\n\n  Modal.prototype.show = function (_relatedTarget) {\n    var that = this\n    var e    = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })\n\n    this.$element.trigger(e)\n\n    if (this.isShown || e.isDefaultPrevented()) return\n\n    this.isShown = true\n\n    this.checkScrollbar()\n    this.setScrollbar()\n    this.$body.addClass('modal-open')\n\n    this.escape()\n    this.resize()\n\n    this.$element.on('click.dismiss.bs.modal', '[data-dismiss=\"modal\"]', $.proxy(this.hide, this))\n\n    this.backdrop(function () {\n      var transition = $.support.transition && that.$element.hasClass('fade')\n\n      if (!that.$element.parent().length) {\n        that.$element.appendTo(that.$body) // don't move modals dom position\n      }\n\n      that.$element\n        .show()\n        .scrollTop(0)\n\n      if (that.options.backdrop) that.adjustBackdrop()\n      that.adjustDialog()\n\n      if (transition) {\n        that.$element[0].offsetWidth // force reflow\n      }\n\n      that.$element\n        .addClass('in')\n        .attr('aria-hidden', false)\n\n      that.enforceFocus()\n\n      var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })\n\n      transition ?\n        that.$element.find('.modal-dialog') // wait for modal to slide in\n          .one('bsTransitionEnd', function () {\n            that.$element.trigger('focus').trigger(e)\n          })\n          .emulateTransitionEnd(Modal.TRANSITION_DURATION) :\n        that.$element.trigger('focus').trigger(e)\n    })\n  }\n\n  Modal.prototype.hide = function (e) {\n    if (e) e.preventDefault()\n\n    e = $.Event('hide.bs.modal')\n\n    this.$element.trigger(e)\n\n    if (!this.isShown || e.isDefaultPrevented()) return\n\n    this.isShown = false\n\n    this.escape()\n    this.resize()\n\n    $(document).off('focusin.bs.modal')\n\n    this.$element\n      .removeClass('in')\n      .attr('aria-hidden', true)\n      .off('click.dismiss.bs.modal')\n\n    $.support.transition && this.$element.hasClass('fade') ?\n      this.$element\n        .one('bsTransitionEnd', $.proxy(this.hideModal, this))\n        .emulateTransitionEnd(Modal.TRANSITION_DURATION) :\n      this.hideModal()\n  }\n\n  Modal.prototype.enforceFocus = function () {\n    $(document)\n      .off('focusin.bs.modal') // guard against infinite focus loop\n      .on('focusin.bs.modal', $.proxy(function (e) {\n        if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {\n          this.$element.trigger('focus')\n        }\n      }, this))\n  }\n\n  Modal.prototype.escape = function () {\n    if (this.isShown && this.options.keyboard) {\n      this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {\n        e.which == 27 && this.hide()\n      }, this))\n    } else if (!this.isShown) {\n      this.$element.off('keydown.dismiss.bs.modal')\n    }\n  }\n\n  Modal.prototype.resize = function () {\n    if (this.isShown) {\n      $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this))\n    } else {\n      $(window).off('resize.bs.modal')\n    }\n  }\n\n  Modal.prototype.hideModal = function () {\n    var that = this\n    this.$element.hide()\n    this.backdrop(function () {\n      that.$body.removeClass('modal-open')\n      that.resetAdjustments()\n      that.resetScrollbar()\n      that.$element.trigger('hidden.bs.modal')\n    })\n  }\n\n  Modal.prototype.removeBackdrop = function () {\n    this.$backdrop && this.$backdrop.remove()\n    this.$backdrop = null\n  }\n\n  Modal.prototype.backdrop = function (callback) {\n    var that = this\n    var animate = this.$element.hasClass('fade') ? 'fade' : ''\n\n    if (this.isShown && this.options.backdrop) {\n      var doAnimate = $.support.transition && animate\n\n      this.$backdrop = $('<div class=\"modal-backdrop ' + animate + '\" />')\n        .prependTo(this.$element)\n        .on('click.dismiss.bs.modal', $.proxy(function (e) {\n          if (e.target !== e.currentTarget) return\n          this.options.backdrop == 'static'\n            ? this.$element[0].focus.call(this.$element[0])\n            : this.hide.call(this)\n        }, this))\n\n      if (doAnimate) this.$backdrop[0].offsetWidth // force reflow\n\n      this.$backdrop.addClass('in')\n\n      if (!callback) return\n\n      doAnimate ?\n        this.$backdrop\n          .one('bsTransitionEnd', callback)\n          .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :\n        callback()\n\n    } else if (!this.isShown && this.$backdrop) {\n      this.$backdrop.removeClass('in')\n\n      var callbackRemove = function () {\n        that.removeBackdrop()\n        callback && callback()\n      }\n      $.support.transition && this.$element.hasClass('fade') ?\n        this.$backdrop\n          .one('bsTransitionEnd', callbackRemove)\n          .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :\n        callbackRemove()\n\n    } else if (callback) {\n      callback()\n    }\n  }\n\n  // these following methods are used to handle overflowing modals\n\n  Modal.prototype.handleUpdate = function () {\n    if (this.options.backdrop) this.adjustBackdrop()\n    this.adjustDialog()\n  }\n\n  Modal.prototype.adjustBackdrop = function () {\n    this.$backdrop\n      .css('height', 0)\n      .css('height', this.$element[0].scrollHeight)\n  }\n\n  Modal.prototype.adjustDialog = function () {\n    var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight\n\n    this.$element.css({\n      paddingLeft:  !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',\n      paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''\n    })\n  }\n\n  Modal.prototype.resetAdjustments = function () {\n    this.$element.css({\n      paddingLeft: '',\n      paddingRight: ''\n    })\n  }\n\n  Modal.prototype.checkScrollbar = function () {\n    this.bodyIsOverflowing = document.body.scrollHeight > document.documentElement.clientHeight\n    this.scrollbarWidth = this.measureScrollbar()\n  }\n\n  Modal.prototype.setScrollbar = function () {\n    var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)\n    if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)\n  }\n\n  Modal.prototype.resetScrollbar = function () {\n    this.$body.css('padding-right', '')\n  }\n\n  Modal.prototype.measureScrollbar = function () { // thx walsh\n    var scrollDiv = document.createElement('div')\n    scrollDiv.className = 'modal-scrollbar-measure'\n    this.$body.append(scrollDiv)\n    var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth\n    this.$body[0].removeChild(scrollDiv)\n    return scrollbarWidth\n  }\n\n\n  // MODAL PLUGIN DEFINITION\n  // =======================\n\n  function Plugin(option, _relatedTarget) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.modal')\n      var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)\n\n      if (!data) $this.data('bs.modal', (data = new Modal(this, options)))\n      if (typeof option == 'string') data[option](_relatedTarget)\n      else if (options.show) data.show(_relatedTarget)\n    })\n  }\n\n  var old = $.fn.modal\n\n  $.fn.modal             = Plugin\n  $.fn.modal.Constructor = Modal\n\n\n  // MODAL NO CONFLICT\n  // =================\n\n  $.fn.modal.noConflict = function () {\n    $.fn.modal = old\n    return this\n  }\n\n\n  // MODAL DATA-API\n  // ==============\n\n  $(document).on('click.bs.modal.data-api', '[data-toggle=\"modal\"]', function (e) {\n    var $this   = $(this)\n    var href    = $this.attr('href')\n    var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\\s]+$)/, ''))) // strip for ie7\n    var option  = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())\n\n    if ($this.is('a')) e.preventDefault()\n\n    $target.one('show.bs.modal', function (showEvent) {\n      if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown\n      $target.one('hidden.bs.modal', function () {\n        $this.is(':visible') && $this.trigger('focus')\n      })\n    })\n    Plugin.call($target, option, this)\n  })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: tooltip.js v3.3.2\n * http://getbootstrap.com/javascript/#tooltip\n * Inspired by the original jQuery.tipsy by Jason Frame\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // TOOLTIP PUBLIC CLASS DEFINITION\n  // ===============================\n\n  var Tooltip = function (element, options) {\n    this.type       =\n    this.options    =\n    this.enabled    =\n    this.timeout    =\n    this.hoverState =\n    this.$element   = null\n\n    this.init('tooltip', element, options)\n  }\n\n  Tooltip.VERSION  = '3.3.2'\n\n  Tooltip.TRANSITION_DURATION = 150\n\n  Tooltip.DEFAULTS = {\n    animation: true,\n    placement: 'top',\n    selector: false,\n    template: '<div class=\"tooltip\" role=\"tooltip\"><div class=\"tooltip-arrow\"></div><div class=\"tooltip-inner\"></div></div>',\n    trigger: 'hover focus',\n    title: '',\n    delay: 0,\n    html: false,\n    container: false,\n    viewport: {\n      selector: 'body',\n      padding: 0\n    }\n  }\n\n  Tooltip.prototype.init = function (type, element, options) {\n    this.enabled   = true\n    this.type      = type\n    this.$element  = $(element)\n    this.options   = this.getOptions(options)\n    this.$viewport = this.options.viewport && $(this.options.viewport.selector || this.options.viewport)\n\n    var triggers = this.options.trigger.split(' ')\n\n    for (var i = triggers.length; i--;) {\n      var trigger = triggers[i]\n\n      if (trigger == 'click') {\n        this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))\n      } else if (trigger != 'manual') {\n        var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focusin'\n        var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'\n\n        this.$element.on(eventIn  + '.' + this.type, this.options.selector, $.proxy(this.enter, this))\n        this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))\n      }\n    }\n\n    this.options.selector ?\n      (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :\n      this.fixTitle()\n  }\n\n  Tooltip.prototype.getDefaults = function () {\n    return Tooltip.DEFAULTS\n  }\n\n  Tooltip.prototype.getOptions = function (options) {\n    options = $.extend({}, this.getDefaults(), this.$element.data(), options)\n\n    if (options.delay && typeof options.delay == 'number') {\n      options.delay = {\n        show: options.delay,\n        hide: options.delay\n      }\n    }\n\n    return options\n  }\n\n  Tooltip.prototype.getDelegateOptions = function () {\n    var options  = {}\n    var defaults = this.getDefaults()\n\n    this._options && $.each(this._options, function (key, value) {\n      if (defaults[key] != value) options[key] = value\n    })\n\n    return options\n  }\n\n  Tooltip.prototype.enter = function (obj) {\n    var self = obj instanceof this.constructor ?\n      obj : $(obj.currentTarget).data('bs.' + this.type)\n\n    if (self && self.$tip && self.$tip.is(':visible')) {\n      self.hoverState = 'in'\n      return\n    }\n\n    if (!self) {\n      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())\n      $(obj.currentTarget).data('bs.' + this.type, self)\n    }\n\n    clearTimeout(self.timeout)\n\n    self.hoverState = 'in'\n\n    if (!self.options.delay || !self.options.delay.show) return self.show()\n\n    self.timeout = setTimeout(function () {\n      if (self.hoverState == 'in') self.show()\n    }, self.options.delay.show)\n  }\n\n  Tooltip.prototype.leave = function (obj) {\n    var self = obj instanceof this.constructor ?\n      obj : $(obj.currentTarget).data('bs.' + this.type)\n\n    if (!self) {\n      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())\n      $(obj.currentTarget).data('bs.' + this.type, self)\n    }\n\n    clearTimeout(self.timeout)\n\n    self.hoverState = 'out'\n\n    if (!self.options.delay || !self.options.delay.hide) return self.hide()\n\n    self.timeout = setTimeout(function () {\n      if (self.hoverState == 'out') self.hide()\n    }, self.options.delay.hide)\n  }\n\n  Tooltip.prototype.show = function () {\n    var e = $.Event('show.bs.' + this.type)\n\n    if (this.hasContent() && this.enabled) {\n      this.$element.trigger(e)\n\n      var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])\n      if (e.isDefaultPrevented() || !inDom) return\n      var that = this\n\n      var $tip = this.tip()\n\n      var tipId = this.getUID(this.type)\n\n      this.setContent()\n      $tip.attr('id', tipId)\n      this.$element.attr('aria-describedby', tipId)\n\n      if (this.options.animation) $tip.addClass('fade')\n\n      var placement = typeof this.options.placement == 'function' ?\n        this.options.placement.call(this, $tip[0], this.$element[0]) :\n        this.options.placement\n\n      var autoToken = /\\s?auto?\\s?/i\n      var autoPlace = autoToken.test(placement)\n      if (autoPlace) placement = placement.replace(autoToken, '') || 'top'\n\n      $tip\n        .detach()\n        .css({ top: 0, left: 0, display: 'block' })\n        .addClass(placement)\n        .data('bs.' + this.type, this)\n\n      this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)\n\n      var pos          = this.getPosition()\n      var actualWidth  = $tip[0].offsetWidth\n      var actualHeight = $tip[0].offsetHeight\n\n      if (autoPlace) {\n        var orgPlacement = placement\n        var $container   = this.options.container ? $(this.options.container) : this.$element.parent()\n        var containerDim = this.getPosition($container)\n\n        placement = placement == 'bottom' && pos.bottom + actualHeight > containerDim.bottom ? 'top'    :\n                    placement == 'top'    && pos.top    - actualHeight < containerDim.top    ? 'bottom' :\n                    placement == 'right'  && pos.right  + actualWidth  > containerDim.width  ? 'left'   :\n                    placement == 'left'   && pos.left   - actualWidth  < containerDim.left   ? 'right'  :\n                    placement\n\n        $tip\n          .removeClass(orgPlacement)\n          .addClass(placement)\n      }\n\n      var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)\n\n      this.applyPlacement(calculatedOffset, placement)\n\n      var complete = function () {\n        var prevHoverState = that.hoverState\n        that.$element.trigger('shown.bs.' + that.type)\n        that.hoverState = null\n\n        if (prevHoverState == 'out') that.leave(that)\n      }\n\n      $.support.transition && this.$tip.hasClass('fade') ?\n        $tip\n          .one('bsTransitionEnd', complete)\n          .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :\n        complete()\n    }\n  }\n\n  Tooltip.prototype.applyPlacement = function (offset, placement) {\n    var $tip   = this.tip()\n    var width  = $tip[0].offsetWidth\n    var height = $tip[0].offsetHeight\n\n    // manually read margins because getBoundingClientRect includes difference\n    var marginTop = parseInt($tip.css('margin-top'), 10)\n    var marginLeft = parseInt($tip.css('margin-left'), 10)\n\n    // we must check for NaN for ie 8/9\n    if (isNaN(marginTop))  marginTop  = 0\n    if (isNaN(marginLeft)) marginLeft = 0\n\n    offset.top  = offset.top  + marginTop\n    offset.left = offset.left + marginLeft\n\n    // $.fn.offset doesn't round pixel values\n    // so we use setOffset directly with our own function B-0\n    $.offset.setOffset($tip[0], $.extend({\n      using: function (props) {\n        $tip.css({\n          top: Math.round(props.top),\n          left: Math.round(props.left)\n        })\n      }\n    }, offset), 0)\n\n    $tip.addClass('in')\n\n    // check to see if placing tip in new offset caused the tip to resize itself\n    var actualWidth  = $tip[0].offsetWidth\n    var actualHeight = $tip[0].offsetHeight\n\n    if (placement == 'top' && actualHeight != height) {\n      offset.top = offset.top + height - actualHeight\n    }\n\n    var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)\n\n    if (delta.left) offset.left += delta.left\n    else offset.top += delta.top\n\n    var isVertical          = /top|bottom/.test(placement)\n    var arrowDelta          = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight\n    var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'\n\n    $tip.offset(offset)\n    this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)\n  }\n\n  Tooltip.prototype.replaceArrow = function (delta, dimension, isHorizontal) {\n    this.arrow()\n      .css(isHorizontal ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')\n      .css(isHorizontal ? 'top' : 'left', '')\n  }\n\n  Tooltip.prototype.setContent = function () {\n    var $tip  = this.tip()\n    var title = this.getTitle()\n\n    $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)\n    $tip.removeClass('fade in top bottom left right')\n  }\n\n  Tooltip.prototype.hide = function (callback) {\n    var that = this\n    var $tip = this.tip()\n    var e    = $.Event('hide.bs.' + this.type)\n\n    function complete() {\n      if (that.hoverState != 'in') $tip.detach()\n      that.$element\n        .removeAttr('aria-describedby')\n        .trigger('hidden.bs.' + that.type)\n      callback && callback()\n    }\n\n    this.$element.trigger(e)\n\n    if (e.isDefaultPrevented()) return\n\n    $tip.removeClass('in')\n\n    $.support.transition && this.$tip.hasClass('fade') ?\n      $tip\n        .one('bsTransitionEnd', complete)\n        .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :\n      complete()\n\n    this.hoverState = null\n\n    return this\n  }\n\n  Tooltip.prototype.fixTitle = function () {\n    var $e = this.$element\n    if ($e.attr('title') || typeof ($e.attr('data-original-title')) != 'string') {\n      $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')\n    }\n  }\n\n  Tooltip.prototype.hasContent = function () {\n    return this.getTitle()\n  }\n\n  Tooltip.prototype.getPosition = function ($element) {\n    $element   = $element || this.$element\n\n    var el     = $element[0]\n    var isBody = el.tagName == 'BODY'\n\n    var elRect    = el.getBoundingClientRect()\n    if (elRect.width == null) {\n      // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093\n      elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })\n    }\n    var elOffset  = isBody ? { top: 0, left: 0 } : $element.offset()\n    var scroll    = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }\n    var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null\n\n    return $.extend({}, elRect, scroll, outerDims, elOffset)\n  }\n\n  Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {\n    return placement == 'bottom' ? { top: pos.top + pos.height,   left: pos.left + pos.width / 2 - actualWidth / 2 } :\n           placement == 'top'    ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :\n           placement == 'left'   ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :\n        /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }\n\n  }\n\n  Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {\n    var delta = { top: 0, left: 0 }\n    if (!this.$viewport) return delta\n\n    var viewportPadding = this.options.viewport && this.options.viewport.padding || 0\n    var viewportDimensions = this.getPosition(this.$viewport)\n\n    if (/right|left/.test(placement)) {\n      var topEdgeOffset    = pos.top - viewportPadding - viewportDimensions.scroll\n      var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight\n      if (topEdgeOffset < viewportDimensions.top) { // top overflow\n        delta.top = viewportDimensions.top - topEdgeOffset\n      } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow\n        delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset\n      }\n    } else {\n      var leftEdgeOffset  = pos.left - viewportPadding\n      var rightEdgeOffset = pos.left + viewportPadding + actualWidth\n      if (leftEdgeOffset < viewportDimensions.left) { // left overflow\n        delta.left = viewportDimensions.left - leftEdgeOffset\n      } else if (rightEdgeOffset > viewportDimensions.width) { // right overflow\n        delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset\n      }\n    }\n\n    return delta\n  }\n\n  Tooltip.prototype.getTitle = function () {\n    var title\n    var $e = this.$element\n    var o  = this.options\n\n    title = $e.attr('data-original-title')\n      || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)\n\n    return title\n  }\n\n  Tooltip.prototype.getUID = function (prefix) {\n    do prefix += ~~(Math.random() * 1000000)\n    while (document.getElementById(prefix))\n    return prefix\n  }\n\n  Tooltip.prototype.tip = function () {\n    return (this.$tip = this.$tip || $(this.options.template))\n  }\n\n  Tooltip.prototype.arrow = function () {\n    return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))\n  }\n\n  Tooltip.prototype.enable = function () {\n    this.enabled = true\n  }\n\n  Tooltip.prototype.disable = function () {\n    this.enabled = false\n  }\n\n  Tooltip.prototype.toggleEnabled = function () {\n    this.enabled = !this.enabled\n  }\n\n  Tooltip.prototype.toggle = function (e) {\n    var self = this\n    if (e) {\n      self = $(e.currentTarget).data('bs.' + this.type)\n      if (!self) {\n        self = new this.constructor(e.currentTarget, this.getDelegateOptions())\n        $(e.currentTarget).data('bs.' + this.type, self)\n      }\n    }\n\n    self.tip().hasClass('in') ? self.leave(self) : self.enter(self)\n  }\n\n  Tooltip.prototype.destroy = function () {\n    var that = this\n    clearTimeout(this.timeout)\n    this.hide(function () {\n      that.$element.off('.' + that.type).removeData('bs.' + that.type)\n    })\n  }\n\n\n  // TOOLTIP PLUGIN DEFINITION\n  // =========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.tooltip')\n      var options = typeof option == 'object' && option\n\n      if (!data && option == 'destroy') return\n      if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.tooltip\n\n  $.fn.tooltip             = Plugin\n  $.fn.tooltip.Constructor = Tooltip\n\n\n  // TOOLTIP NO CONFLICT\n  // ===================\n\n  $.fn.tooltip.noConflict = function () {\n    $.fn.tooltip = old\n    return this\n  }\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: popover.js v3.3.2\n * http://getbootstrap.com/javascript/#popovers\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // POPOVER PUBLIC CLASS DEFINITION\n  // ===============================\n\n  var Popover = function (element, options) {\n    this.init('popover', element, options)\n  }\n\n  if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')\n\n  Popover.VERSION  = '3.3.2'\n\n  Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {\n    placement: 'right',\n    trigger: 'click',\n    content: '',\n    template: '<div class=\"popover\" role=\"tooltip\"><div class=\"arrow\"></div><h3 class=\"popover-title\"></h3><div class=\"popover-content\"></div></div>'\n  })\n\n\n  // NOTE: POPOVER EXTENDS tooltip.js\n  // ================================\n\n  Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)\n\n  Popover.prototype.constructor = Popover\n\n  Popover.prototype.getDefaults = function () {\n    return Popover.DEFAULTS\n  }\n\n  Popover.prototype.setContent = function () {\n    var $tip    = this.tip()\n    var title   = this.getTitle()\n    var content = this.getContent()\n\n    $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)\n    $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events\n      this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'\n    ](content)\n\n    $tip.removeClass('fade top bottom left right in')\n\n    // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do\n    // this manually by checking the contents.\n    if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()\n  }\n\n  Popover.prototype.hasContent = function () {\n    return this.getTitle() || this.getContent()\n  }\n\n  Popover.prototype.getContent = function () {\n    var $e = this.$element\n    var o  = this.options\n\n    return $e.attr('data-content')\n      || (typeof o.content == 'function' ?\n            o.content.call($e[0]) :\n            o.content)\n  }\n\n  Popover.prototype.arrow = function () {\n    return (this.$arrow = this.$arrow || this.tip().find('.arrow'))\n  }\n\n  Popover.prototype.tip = function () {\n    if (!this.$tip) this.$tip = $(this.options.template)\n    return this.$tip\n  }\n\n\n  // POPOVER PLUGIN DEFINITION\n  // =========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.popover')\n      var options = typeof option == 'object' && option\n\n      if (!data && option == 'destroy') return\n      if (!data) $this.data('bs.popover', (data = new Popover(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.popover\n\n  $.fn.popover             = Plugin\n  $.fn.popover.Constructor = Popover\n\n\n  // POPOVER NO CONFLICT\n  // ===================\n\n  $.fn.popover.noConflict = function () {\n    $.fn.popover = old\n    return this\n  }\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: scrollspy.js v3.3.2\n * http://getbootstrap.com/javascript/#scrollspy\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // SCROLLSPY CLASS DEFINITION\n  // ==========================\n\n  function ScrollSpy(element, options) {\n    var process  = $.proxy(this.process, this)\n\n    this.$body          = $('body')\n    this.$scrollElement = $(element).is('body') ? $(window) : $(element)\n    this.options        = $.extend({}, ScrollSpy.DEFAULTS, options)\n    this.selector       = (this.options.target || '') + ' .nav li > a'\n    this.offsets        = []\n    this.targets        = []\n    this.activeTarget   = null\n    this.scrollHeight   = 0\n\n    this.$scrollElement.on('scroll.bs.scrollspy', process)\n    this.refresh()\n    this.process()\n  }\n\n  ScrollSpy.VERSION  = '3.3.2'\n\n  ScrollSpy.DEFAULTS = {\n    offset: 10\n  }\n\n  ScrollSpy.prototype.getScrollHeight = function () {\n    return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)\n  }\n\n  ScrollSpy.prototype.refresh = function () {\n    var offsetMethod = 'offset'\n    var offsetBase   = 0\n\n    if (!$.isWindow(this.$scrollElement[0])) {\n      offsetMethod = 'position'\n      offsetBase   = this.$scrollElement.scrollTop()\n    }\n\n    this.offsets = []\n    this.targets = []\n    this.scrollHeight = this.getScrollHeight()\n\n    var self     = this\n\n    this.$body\n      .find(this.selector)\n      .map(function () {\n        var $el   = $(this)\n        var href  = $el.data('target') || $el.attr('href')\n        var $href = /^#./.test(href) && $(href)\n\n        return ($href\n          && $href.length\n          && $href.is(':visible')\n          && [[$href[offsetMethod]().top + offsetBase, href]]) || null\n      })\n      .sort(function (a, b) { return a[0] - b[0] })\n      .each(function () {\n        self.offsets.push(this[0])\n        self.targets.push(this[1])\n      })\n  }\n\n  ScrollSpy.prototype.process = function () {\n    var scrollTop    = this.$scrollElement.scrollTop() + this.options.offset\n    var scrollHeight = this.getScrollHeight()\n    var maxScroll    = this.options.offset + scrollHeight - this.$scrollElement.height()\n    var offsets      = this.offsets\n    var targets      = this.targets\n    var activeTarget = this.activeTarget\n    var i\n\n    if (this.scrollHeight != scrollHeight) {\n      this.refresh()\n    }\n\n    if (scrollTop >= maxScroll) {\n      return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)\n    }\n\n    if (activeTarget && scrollTop < offsets[0]) {\n      this.activeTarget = null\n      return this.clear()\n    }\n\n    for (i = offsets.length; i--;) {\n      activeTarget != targets[i]\n        && scrollTop >= offsets[i]\n        && (!offsets[i + 1] || scrollTop <= offsets[i + 1])\n        && this.activate(targets[i])\n    }\n  }\n\n  ScrollSpy.prototype.activate = function (target) {\n    this.activeTarget = target\n\n    this.clear()\n\n    var selector = this.selector +\n        '[data-target=\"' + target + '\"],' +\n        this.selector + '[href=\"' + target + '\"]'\n\n    var active = $(selector)\n      .parents('li')\n      .addClass('active')\n\n    if (active.parent('.dropdown-menu').length) {\n      active = active\n        .closest('li.dropdown')\n        .addClass('active')\n    }\n\n    active.trigger('activate.bs.scrollspy')\n  }\n\n  ScrollSpy.prototype.clear = function () {\n    $(this.selector)\n      .parentsUntil(this.options.target, '.active')\n      .removeClass('active')\n  }\n\n\n  // SCROLLSPY PLUGIN DEFINITION\n  // ===========================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.scrollspy')\n      var options = typeof option == 'object' && option\n\n      if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.scrollspy\n\n  $.fn.scrollspy             = Plugin\n  $.fn.scrollspy.Constructor = ScrollSpy\n\n\n  // SCROLLSPY NO CONFLICT\n  // =====================\n\n  $.fn.scrollspy.noConflict = function () {\n    $.fn.scrollspy = old\n    return this\n  }\n\n\n  // SCROLLSPY DATA-API\n  // ==================\n\n  $(window).on('load.bs.scrollspy.data-api', function () {\n    $('[data-spy=\"scroll\"]').each(function () {\n      var $spy = $(this)\n      Plugin.call($spy, $spy.data())\n    })\n  })\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: tab.js v3.3.2\n * http://getbootstrap.com/javascript/#tabs\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // TAB CLASS DEFINITION\n  // ====================\n\n  var Tab = function (element) {\n    this.element = $(element)\n  }\n\n  Tab.VERSION = '3.3.2'\n\n  Tab.TRANSITION_DURATION = 150\n\n  Tab.prototype.show = function () {\n    var $this    = this.element\n    var $ul      = $this.closest('ul:not(.dropdown-menu)')\n    var selector = $this.data('target')\n\n    if (!selector) {\n      selector = $this.attr('href')\n      selector = selector && selector.replace(/.*(?=#[^\\s]*$)/, '') // strip for ie7\n    }\n\n    if ($this.parent('li').hasClass('active')) return\n\n    var $previous = $ul.find('.active:last a')\n    var hideEvent = $.Event('hide.bs.tab', {\n      relatedTarget: $this[0]\n    })\n    var showEvent = $.Event('show.bs.tab', {\n      relatedTarget: $previous[0]\n    })\n\n    $previous.trigger(hideEvent)\n    $this.trigger(showEvent)\n\n    if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return\n\n    var $target = $(selector)\n\n    this.activate($this.closest('li'), $ul)\n    this.activate($target, $target.parent(), function () {\n      $previous.trigger({\n        type: 'hidden.bs.tab',\n        relatedTarget: $this[0]\n      })\n      $this.trigger({\n        type: 'shown.bs.tab',\n        relatedTarget: $previous[0]\n      })\n    })\n  }\n\n  Tab.prototype.activate = function (element, container, callback) {\n    var $active    = container.find('> .active')\n    var transition = callback\n      && $.support.transition\n      && (($active.length && $active.hasClass('fade')) || !!container.find('> .fade').length)\n\n    function next() {\n      $active\n        .removeClass('active')\n        .find('> .dropdown-menu > .active')\n          .removeClass('active')\n        .end()\n        .find('[data-toggle=\"tab\"]')\n          .attr('aria-expanded', false)\n\n      element\n        .addClass('active')\n        .find('[data-toggle=\"tab\"]')\n          .attr('aria-expanded', true)\n\n      if (transition) {\n        element[0].offsetWidth // reflow for transition\n        element.addClass('in')\n      } else {\n        element.removeClass('fade')\n      }\n\n      if (element.parent('.dropdown-menu')) {\n        element\n          .closest('li.dropdown')\n            .addClass('active')\n          .end()\n          .find('[data-toggle=\"tab\"]')\n            .attr('aria-expanded', true)\n      }\n\n      callback && callback()\n    }\n\n    $active.length && transition ?\n      $active\n        .one('bsTransitionEnd', next)\n        .emulateTransitionEnd(Tab.TRANSITION_DURATION) :\n      next()\n\n    $active.removeClass('in')\n  }\n\n\n  // TAB PLUGIN DEFINITION\n  // =====================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this = $(this)\n      var data  = $this.data('bs.tab')\n\n      if (!data) $this.data('bs.tab', (data = new Tab(this)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.tab\n\n  $.fn.tab             = Plugin\n  $.fn.tab.Constructor = Tab\n\n\n  // TAB NO CONFLICT\n  // ===============\n\n  $.fn.tab.noConflict = function () {\n    $.fn.tab = old\n    return this\n  }\n\n\n  // TAB DATA-API\n  // ============\n\n  var clickHandler = function (e) {\n    e.preventDefault()\n    Plugin.call($(this), 'show')\n  }\n\n  $(document)\n    .on('click.bs.tab.data-api', '[data-toggle=\"tab\"]', clickHandler)\n    .on('click.bs.tab.data-api', '[data-toggle=\"pill\"]', clickHandler)\n\n}(jQuery);\n\n/* ========================================================================\n * Bootstrap: affix.js v3.3.2\n * http://getbootstrap.com/javascript/#affix\n * ========================================================================\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n  'use strict';\n\n  // AFFIX CLASS DEFINITION\n  // ======================\n\n  var Affix = function (element, options) {\n    this.options = $.extend({}, Affix.DEFAULTS, options)\n\n    this.$target = $(this.options.target)\n      .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))\n      .on('click.bs.affix.data-api',  $.proxy(this.checkPositionWithEventLoop, this))\n\n    this.$element     = $(element)\n    this.affixed      =\n    this.unpin        =\n    this.pinnedOffset = null\n\n    this.checkPosition()\n  }\n\n  Affix.VERSION  = '3.3.2'\n\n  Affix.RESET    = 'affix affix-top affix-bottom'\n\n  Affix.DEFAULTS = {\n    offset: 0,\n    target: window\n  }\n\n  Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) {\n    var scrollTop    = this.$target.scrollTop()\n    var position     = this.$element.offset()\n    var targetHeight = this.$target.height()\n\n    if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false\n\n    if (this.affixed == 'bottom') {\n      if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom'\n      return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom'\n    }\n\n    var initializing   = this.affixed == null\n    var colliderTop    = initializing ? scrollTop : position.top\n    var colliderHeight = initializing ? targetHeight : height\n\n    if (offsetTop != null && scrollTop <= offsetTop) return 'top'\n    if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'\n\n    return false\n  }\n\n  Affix.prototype.getPinnedOffset = function () {\n    if (this.pinnedOffset) return this.pinnedOffset\n    this.$element.removeClass(Affix.RESET).addClass('affix')\n    var scrollTop = this.$target.scrollTop()\n    var position  = this.$element.offset()\n    return (this.pinnedOffset = position.top - scrollTop)\n  }\n\n  Affix.prototype.checkPositionWithEventLoop = function () {\n    setTimeout($.proxy(this.checkPosition, this), 1)\n  }\n\n  Affix.prototype.checkPosition = function () {\n    if (!this.$element.is(':visible')) return\n\n    var height       = this.$element.height()\n    var offset       = this.options.offset\n    var offsetTop    = offset.top\n    var offsetBottom = offset.bottom\n    var scrollHeight = $('body').height()\n\n    if (typeof offset != 'object')         offsetBottom = offsetTop = offset\n    if (typeof offsetTop == 'function')    offsetTop    = offset.top(this.$element)\n    if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)\n\n    var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom)\n\n    if (this.affixed != affix) {\n      if (this.unpin != null) this.$element.css('top', '')\n\n      var affixType = 'affix' + (affix ? '-' + affix : '')\n      var e         = $.Event(affixType + '.bs.affix')\n\n      this.$element.trigger(e)\n\n      if (e.isDefaultPrevented()) return\n\n      this.affixed = affix\n      this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null\n\n      this.$element\n        .removeClass(Affix.RESET)\n        .addClass(affixType)\n        .trigger(affixType.replace('affix', 'affixed') + '.bs.affix')\n    }\n\n    if (affix == 'bottom') {\n      this.$element.offset({\n        top: scrollHeight - height - offsetBottom\n      })\n    }\n  }\n\n\n  // AFFIX PLUGIN DEFINITION\n  // =======================\n\n  function Plugin(option) {\n    return this.each(function () {\n      var $this   = $(this)\n      var data    = $this.data('bs.affix')\n      var options = typeof option == 'object' && option\n\n      if (!data) $this.data('bs.affix', (data = new Affix(this, options)))\n      if (typeof option == 'string') data[option]()\n    })\n  }\n\n  var old = $.fn.affix\n\n  $.fn.affix             = Plugin\n  $.fn.affix.Constructor = Affix\n\n\n  // AFFIX NO CONFLICT\n  // =================\n\n  $.fn.affix.noConflict = function () {\n    $.fn.affix = old\n    return this\n  }\n\n\n  // AFFIX DATA-API\n  // ==============\n\n  $(window).on('load', function () {\n    $('[data-spy=\"affix\"]').each(function () {\n      var $spy = $(this)\n      var data = $spy.data()\n\n      data.offset = data.offset || {}\n\n      if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom\n      if (data.offsetTop    != null) data.offset.top    = data.offsetTop\n\n      Plugin.call($spy, data)\n    })\n  })\n\n}(jQuery);\n"
  },
  {
    "path": "js/hux-blog.js",
    "content": "/*!\n * Clean Blog v1.0.0 (http://startbootstrap.com)\n * Copyright 2015 Start Bootstrap\n * Licensed under Apache 2.0 (https://github.com/IronSummitMedia/startbootstrap/blob/gh-pages/LICENSE)\n */\n\n /*!\n * Hux Blog v1.6.0 (http://startbootstrap.com)\n * Copyright 2016 @huxpro\n * Licensed under Apache 2.0 \n */\n\n// Tooltip Init\n// Unuse by Hux since V1.6: Titles now display by default so there is no need for tooltip\n// $(function() {\n//     $(\"[data-toggle='tooltip']\").tooltip();\n// });\n\n\n// make all images responsive\n/* \n * Unuse by Hux\n * actually only Portfolio-Pages can't use it and only post-img need it.\n * so I modify the _layout/post and CSS to make post-img responsive!\n */\n// $(function() {\n//  $(\"img\").addClass(\"img-responsive\");\n// });\n\n// responsive tables\n$(document).ready(function() {\n    $(\"table\").wrap(\"<div class='table-responsive'></div>\");\n    $(\"table\").addClass(\"table\");\n});\n\n// responsive embed videos\n$(document).ready(function() {\n    $('iframe[src*=\"youtube.com\"]').wrap('<div class=\"embed-responsive embed-responsive-16by9\"></div>');\n    $('iframe[src*=\"youtube.com\"]').addClass('embed-responsive-item');\n    $('iframe[src*=\"vimeo.com\"]').wrap('<div class=\"embed-responsive embed-responsive-16by9\"></div>');\n    $('iframe[src*=\"vimeo.com\"]').addClass('embed-responsive-item');\n});\n\n// Navigation Scripts to Show Header on Scroll-Up\njQuery(document).ready(function($) {\n    var MQL = 1170;\n\n    //primary navigation slide-in effect\n    if ($(window).width() > MQL) {\n        var headerHeight = $('.navbar-custom').height(),\n            bannerHeight  = $('.intro-header .container').height();     \n        $(window).on('scroll', {\n                previousTop: 0\n            },\n            function() {\n                var currentTop = $(window).scrollTop(),\n                    $catalog = $('.side-catalog');\n\n                //check if user is scrolling up by mouse or keyborad\n                if (currentTop < this.previousTop) {\n                    //if scrolling up...\n                    if (currentTop > 0 && $('.navbar-custom').hasClass('is-fixed')) {\n                        $('.navbar-custom').addClass('is-visible');\n                    } else {\n                        $('.navbar-custom').removeClass('is-visible is-fixed');\n                    }\n                } else {\n                    //if scrolling down...\n                    $('.navbar-custom').removeClass('is-visible');\n                    if (currentTop > headerHeight && !$('.navbar-custom').hasClass('is-fixed')) $('.navbar-custom').addClass('is-fixed');\n                }\n                this.previousTop = currentTop;\n\n\n                //adjust the appearance of side-catalog\n                $catalog.show()\n                if (currentTop > (bannerHeight + 41)) {\n                    $catalog.addClass('fixed')\n                } else {\n                    $catalog.removeClass('fixed')\n                }\n            });\n    }\n});"
  },
  {
    "path": "js/jquery.js",
    "content": "/*!\n * jQuery JavaScript Library v2.1.3\n * http://jquery.com/\n *\n * Includes Sizzle.js\n * http://sizzlejs.com/\n *\n * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors\n * Released under the MIT license\n * http://jquery.org/license\n *\n * Date: 2014-12-18T15:11Z\n */\n\n(function( global, factory ) {\n\n\tif ( typeof module === \"object\" && typeof module.exports === \"object\" ) {\n\t\t// For CommonJS and CommonJS-like environments where a proper `window`\n\t\t// is present, execute the factory and get jQuery.\n\t\t// For environments that do not have a `window` with a `document`\n\t\t// (such as Node.js), expose a factory as module.exports.\n\t\t// This accentuates the need for the creation of a real `window`.\n\t\t// e.g. var jQuery = require(\"jquery\")(window);\n\t\t// See ticket #14549 for more info.\n\t\tmodule.exports = global.document ?\n\t\t\tfactory( global, true ) :\n\t\t\tfunction( w ) {\n\t\t\t\tif ( !w.document ) {\n\t\t\t\t\tthrow new Error( \"jQuery requires a window with a document\" );\n\t\t\t\t}\n\t\t\t\treturn factory( w );\n\t\t\t};\n\t} else {\n\t\tfactory( global );\n\t}\n\n// Pass this if window is not defined yet\n}(typeof window !== \"undefined\" ? window : this, function( window, noGlobal ) {\n\n// Support: Firefox 18+\n// Can't be in strict mode, several libs including ASP.NET trace\n// the stack via arguments.caller.callee and Firefox dies if\n// you try to trace through \"use strict\" call chains. (#13335)\n//\n\nvar arr = [];\n\nvar slice = arr.slice;\n\nvar concat = arr.concat;\n\nvar push = arr.push;\n\nvar indexOf = arr.indexOf;\n\nvar class2type = {};\n\nvar toString = class2type.toString;\n\nvar hasOwn = class2type.hasOwnProperty;\n\nvar support = {};\n\n\n\nvar\n\t// Use the correct document accordingly with window argument (sandbox)\n\tdocument = window.document,\n\n\tversion = \"2.1.3\",\n\n\t// Define a local copy of jQuery\n\tjQuery = function( selector, context ) {\n\t\t// The jQuery object is actually just the init constructor 'enhanced'\n\t\t// Need init if jQuery is called (just allow error to be thrown if not included)\n\t\treturn new jQuery.fn.init( selector, context );\n\t},\n\n\t// Support: Android<4.1\n\t// Make sure we trim BOM and NBSP\n\trtrim = /^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g,\n\n\t// Matches dashed string for camelizing\n\trmsPrefix = /^-ms-/,\n\trdashAlpha = /-([\\da-z])/gi,\n\n\t// Used by jQuery.camelCase as callback to replace()\n\tfcamelCase = function( all, letter ) {\n\t\treturn letter.toUpperCase();\n\t};\n\njQuery.fn = jQuery.prototype = {\n\t// The current version of jQuery being used\n\tjquery: version,\n\n\tconstructor: jQuery,\n\n\t// Start with an empty selector\n\tselector: \"\",\n\n\t// The default length of a jQuery object is 0\n\tlength: 0,\n\n\ttoArray: function() {\n\t\treturn slice.call( this );\n\t},\n\n\t// Get the Nth element in the matched element set OR\n\t// Get the whole matched element set as a clean array\n\tget: function( num ) {\n\t\treturn num != null ?\n\n\t\t\t// Return just the one element from the set\n\t\t\t( num < 0 ? this[ num + this.length ] : this[ num ] ) :\n\n\t\t\t// Return all the elements in a clean array\n\t\t\tslice.call( this );\n\t},\n\n\t// Take an array of elements and push it onto the stack\n\t// (returning the new matched element set)\n\tpushStack: function( elems ) {\n\n\t\t// Build a new jQuery matched element set\n\t\tvar ret = jQuery.merge( this.constructor(), elems );\n\n\t\t// Add the old object onto the stack (as a reference)\n\t\tret.prevObject = this;\n\t\tret.context = this.context;\n\n\t\t// Return the newly-formed element set\n\t\treturn ret;\n\t},\n\n\t// Execute a callback for every element in the matched set.\n\t// (You can seed the arguments with an array of args, but this is\n\t// only used internally.)\n\teach: function( callback, args ) {\n\t\treturn jQuery.each( this, callback, args );\n\t},\n\n\tmap: function( callback ) {\n\t\treturn this.pushStack( jQuery.map(this, function( elem, i ) {\n\t\t\treturn callback.call( elem, i, elem );\n\t\t}));\n\t},\n\n\tslice: function() {\n\t\treturn this.pushStack( slice.apply( this, arguments ) );\n\t},\n\n\tfirst: function() {\n\t\treturn this.eq( 0 );\n\t},\n\n\tlast: function() {\n\t\treturn this.eq( -1 );\n\t},\n\n\teq: function( i ) {\n\t\tvar len = this.length,\n\t\t\tj = +i + ( i < 0 ? len : 0 );\n\t\treturn this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );\n\t},\n\n\tend: function() {\n\t\treturn this.prevObject || this.constructor(null);\n\t},\n\n\t// For internal use only.\n\t// Behaves like an Array's method, not like a jQuery method.\n\tpush: push,\n\tsort: arr.sort,\n\tsplice: arr.splice\n};\n\njQuery.extend = jQuery.fn.extend = function() {\n\tvar options, name, src, copy, copyIsArray, clone,\n\t\ttarget = arguments[0] || {},\n\t\ti = 1,\n\t\tlength = arguments.length,\n\t\tdeep = false;\n\n\t// Handle a deep copy situation\n\tif ( typeof target === \"boolean\" ) {\n\t\tdeep = target;\n\n\t\t// Skip the boolean and the target\n\t\ttarget = arguments[ i ] || {};\n\t\ti++;\n\t}\n\n\t// Handle case when target is a string or something (possible in deep copy)\n\tif ( typeof target !== \"object\" && !jQuery.isFunction(target) ) {\n\t\ttarget = {};\n\t}\n\n\t// Extend jQuery itself if only one argument is passed\n\tif ( i === length ) {\n\t\ttarget = this;\n\t\ti--;\n\t}\n\n\tfor ( ; i < length; i++ ) {\n\t\t// Only deal with non-null/undefined values\n\t\tif ( (options = arguments[ i ]) != null ) {\n\t\t\t// Extend the base object\n\t\t\tfor ( name in options ) {\n\t\t\t\tsrc = target[ name ];\n\t\t\t\tcopy = options[ name ];\n\n\t\t\t\t// Prevent never-ending loop\n\t\t\t\tif ( target === copy ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Recurse if we're merging plain objects or arrays\n\t\t\t\tif ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {\n\t\t\t\t\tif ( copyIsArray ) {\n\t\t\t\t\t\tcopyIsArray = false;\n\t\t\t\t\t\tclone = src && jQuery.isArray(src) ? src : [];\n\n\t\t\t\t\t} else {\n\t\t\t\t\t\tclone = src && jQuery.isPlainObject(src) ? src : {};\n\t\t\t\t\t}\n\n\t\t\t\t\t// Never move original objects, clone them\n\t\t\t\t\ttarget[ name ] = jQuery.extend( deep, clone, copy );\n\n\t\t\t\t// Don't bring in undefined values\n\t\t\t\t} else if ( copy !== undefined ) {\n\t\t\t\t\ttarget[ name ] = copy;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Return the modified object\n\treturn target;\n};\n\njQuery.extend({\n\t// Unique for each copy of jQuery on the page\n\texpando: \"jQuery\" + ( version + Math.random() ).replace( /\\D/g, \"\" ),\n\n\t// Assume jQuery is ready without the ready module\n\tisReady: true,\n\n\terror: function( msg ) {\n\t\tthrow new Error( msg );\n\t},\n\n\tnoop: function() {},\n\n\tisFunction: function( obj ) {\n\t\treturn jQuery.type(obj) === \"function\";\n\t},\n\n\tisArray: Array.isArray,\n\n\tisWindow: function( obj ) {\n\t\treturn obj != null && obj === obj.window;\n\t},\n\n\tisNumeric: function( obj ) {\n\t\t// parseFloat NaNs numeric-cast false positives (null|true|false|\"\")\n\t\t// ...but misinterprets leading-number strings, particularly hex literals (\"0x...\")\n\t\t// subtraction forces infinities to NaN\n\t\t// adding 1 corrects loss of precision from parseFloat (#15100)\n\t\treturn !jQuery.isArray( obj ) && (obj - parseFloat( obj ) + 1) >= 0;\n\t},\n\n\tisPlainObject: function( obj ) {\n\t\t// Not plain objects:\n\t\t// - Any object or value whose internal [[Class]] property is not \"[object Object]\"\n\t\t// - DOM nodes\n\t\t// - window\n\t\tif ( jQuery.type( obj ) !== \"object\" || obj.nodeType || jQuery.isWindow( obj ) ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif ( obj.constructor &&\n\t\t\t\t!hasOwn.call( obj.constructor.prototype, \"isPrototypeOf\" ) ) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// If the function hasn't returned already, we're confident that\n\t\t// |obj| is a plain object, created by {} or constructed with new Object\n\t\treturn true;\n\t},\n\n\tisEmptyObject: function( obj ) {\n\t\tvar name;\n\t\tfor ( name in obj ) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t},\n\n\ttype: function( obj ) {\n\t\tif ( obj == null ) {\n\t\t\treturn obj + \"\";\n\t\t}\n\t\t// Support: Android<4.0, iOS<6 (functionish RegExp)\n\t\treturn typeof obj === \"object\" || typeof obj === \"function\" ?\n\t\t\tclass2type[ toString.call(obj) ] || \"object\" :\n\t\t\ttypeof obj;\n\t},\n\n\t// Evaluates a script in a global context\n\tglobalEval: function( code ) {\n\t\tvar script,\n\t\t\tindirect = eval;\n\n\t\tcode = jQuery.trim( code );\n\n\t\tif ( code ) {\n\t\t\t// If the code includes a valid, prologue position\n\t\t\t// strict mode pragma, execute code by injecting a\n\t\t\t// script tag into the document.\n\t\t\tif ( code.indexOf(\"use strict\") === 1 ) {\n\t\t\t\tscript = document.createElement(\"script\");\n\t\t\t\tscript.text = code;\n\t\t\t\tdocument.head.appendChild( script ).parentNode.removeChild( script );\n\t\t\t} else {\n\t\t\t// Otherwise, avoid the DOM node creation, insertion\n\t\t\t// and removal by using an indirect global eval\n\t\t\t\tindirect( code );\n\t\t\t}\n\t\t}\n\t},\n\n\t// Convert dashed to camelCase; used by the css and data modules\n\t// Support: IE9-11+\n\t// Microsoft forgot to hump their vendor prefix (#9572)\n\tcamelCase: function( string ) {\n\t\treturn string.replace( rmsPrefix, \"ms-\" ).replace( rdashAlpha, fcamelCase );\n\t},\n\n\tnodeName: function( elem, name ) {\n\t\treturn elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();\n\t},\n\n\t// args is for internal usage only\n\teach: function( obj, callback, args ) {\n\t\tvar value,\n\t\t\ti = 0,\n\t\t\tlength = obj.length,\n\t\t\tisArray = isArraylike( obj );\n\n\t\tif ( args ) {\n\t\t\tif ( isArray ) {\n\t\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\t\tvalue = callback.apply( obj[ i ], args );\n\n\t\t\t\t\tif ( value === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor ( i in obj ) {\n\t\t\t\t\tvalue = callback.apply( obj[ i ], args );\n\n\t\t\t\t\tif ( value === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t// A special, fast, case for the most common use of each\n\t\t} else {\n\t\t\tif ( isArray ) {\n\t\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\t\tvalue = callback.call( obj[ i ], i, obj[ i ] );\n\n\t\t\t\t\tif ( value === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor ( i in obj ) {\n\t\t\t\t\tvalue = callback.call( obj[ i ], i, obj[ i ] );\n\n\t\t\t\t\tif ( value === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn obj;\n\t},\n\n\t// Support: Android<4.1\n\ttrim: function( text ) {\n\t\treturn text == null ?\n\t\t\t\"\" :\n\t\t\t( text + \"\" ).replace( rtrim, \"\" );\n\t},\n\n\t// results is for internal usage only\n\tmakeArray: function( arr, results ) {\n\t\tvar ret = results || [];\n\n\t\tif ( arr != null ) {\n\t\t\tif ( isArraylike( Object(arr) ) ) {\n\t\t\t\tjQuery.merge( ret,\n\t\t\t\t\ttypeof arr === \"string\" ?\n\t\t\t\t\t[ arr ] : arr\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tpush.call( ret, arr );\n\t\t\t}\n\t\t}\n\n\t\treturn ret;\n\t},\n\n\tinArray: function( elem, arr, i ) {\n\t\treturn arr == null ? -1 : indexOf.call( arr, elem, i );\n\t},\n\n\tmerge: function( first, second ) {\n\t\tvar len = +second.length,\n\t\t\tj = 0,\n\t\t\ti = first.length;\n\n\t\tfor ( ; j < len; j++ ) {\n\t\t\tfirst[ i++ ] = second[ j ];\n\t\t}\n\n\t\tfirst.length = i;\n\n\t\treturn first;\n\t},\n\n\tgrep: function( elems, callback, invert ) {\n\t\tvar callbackInverse,\n\t\t\tmatches = [],\n\t\t\ti = 0,\n\t\t\tlength = elems.length,\n\t\t\tcallbackExpect = !invert;\n\n\t\t// Go through the array, only saving the items\n\t\t// that pass the validator function\n\t\tfor ( ; i < length; i++ ) {\n\t\t\tcallbackInverse = !callback( elems[ i ], i );\n\t\t\tif ( callbackInverse !== callbackExpect ) {\n\t\t\t\tmatches.push( elems[ i ] );\n\t\t\t}\n\t\t}\n\n\t\treturn matches;\n\t},\n\n\t// arg is for internal usage only\n\tmap: function( elems, callback, arg ) {\n\t\tvar value,\n\t\t\ti = 0,\n\t\t\tlength = elems.length,\n\t\t\tisArray = isArraylike( elems ),\n\t\t\tret = [];\n\n\t\t// Go through the array, translating each of the items to their new values\n\t\tif ( isArray ) {\n\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\tvalue = callback( elems[ i ], i, arg );\n\n\t\t\t\tif ( value != null ) {\n\t\t\t\t\tret.push( value );\n\t\t\t\t}\n\t\t\t}\n\n\t\t// Go through every key on the object,\n\t\t} else {\n\t\t\tfor ( i in elems ) {\n\t\t\t\tvalue = callback( elems[ i ], i, arg );\n\n\t\t\t\tif ( value != null ) {\n\t\t\t\t\tret.push( value );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Flatten any nested arrays\n\t\treturn concat.apply( [], ret );\n\t},\n\n\t// A global GUID counter for objects\n\tguid: 1,\n\n\t// Bind a function to a context, optionally partially applying any\n\t// arguments.\n\tproxy: function( fn, context ) {\n\t\tvar tmp, args, proxy;\n\n\t\tif ( typeof context === \"string\" ) {\n\t\t\ttmp = fn[ context ];\n\t\t\tcontext = fn;\n\t\t\tfn = tmp;\n\t\t}\n\n\t\t// Quick check to determine if target is callable, in the spec\n\t\t// this throws a TypeError, but we will just return undefined.\n\t\tif ( !jQuery.isFunction( fn ) ) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\t// Simulated bind\n\t\targs = slice.call( arguments, 2 );\n\t\tproxy = function() {\n\t\t\treturn fn.apply( context || this, args.concat( slice.call( arguments ) ) );\n\t\t};\n\n\t\t// Set the guid of unique handler to the same of original handler, so it can be removed\n\t\tproxy.guid = fn.guid = fn.guid || jQuery.guid++;\n\n\t\treturn proxy;\n\t},\n\n\tnow: Date.now,\n\n\t// jQuery.support is not used in Core but other projects attach their\n\t// properties to it so it needs to exist.\n\tsupport: support\n});\n\n// Populate the class2type map\njQuery.each(\"Boolean Number String Function Array Date RegExp Object Error\".split(\" \"), function(i, name) {\n\tclass2type[ \"[object \" + name + \"]\" ] = name.toLowerCase();\n});\n\nfunction isArraylike( obj ) {\n\tvar length = obj.length,\n\t\ttype = jQuery.type( obj );\n\n\tif ( type === \"function\" || jQuery.isWindow( obj ) ) {\n\t\treturn false;\n\t}\n\n\tif ( obj.nodeType === 1 && length ) {\n\t\treturn true;\n\t}\n\n\treturn type === \"array\" || length === 0 ||\n\t\ttypeof length === \"number\" && length > 0 && ( length - 1 ) in obj;\n}\nvar Sizzle =\n/*!\n * Sizzle CSS Selector Engine v2.2.0-pre\n * http://sizzlejs.com/\n *\n * Copyright 2008, 2014 jQuery Foundation, Inc. and other contributors\n * Released under the MIT license\n * http://jquery.org/license\n *\n * Date: 2014-12-16\n */\n(function( window ) {\n\nvar i,\n\tsupport,\n\tExpr,\n\tgetText,\n\tisXML,\n\ttokenize,\n\tcompile,\n\tselect,\n\toutermostContext,\n\tsortInput,\n\thasDuplicate,\n\n\t// Local document vars\n\tsetDocument,\n\tdocument,\n\tdocElem,\n\tdocumentIsHTML,\n\trbuggyQSA,\n\trbuggyMatches,\n\tmatches,\n\tcontains,\n\n\t// Instance-specific data\n\texpando = \"sizzle\" + 1 * new Date(),\n\tpreferredDoc = window.document,\n\tdirruns = 0,\n\tdone = 0,\n\tclassCache = createCache(),\n\ttokenCache = createCache(),\n\tcompilerCache = createCache(),\n\tsortOrder = function( a, b ) {\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t}\n\t\treturn 0;\n\t},\n\n\t// General-purpose constants\n\tMAX_NEGATIVE = 1 << 31,\n\n\t// Instance methods\n\thasOwn = ({}).hasOwnProperty,\n\tarr = [],\n\tpop = arr.pop,\n\tpush_native = arr.push,\n\tpush = arr.push,\n\tslice = arr.slice,\n\t// Use a stripped-down indexOf as it's faster than native\n\t// http://jsperf.com/thor-indexof-vs-for/5\n\tindexOf = function( list, elem ) {\n\t\tvar i = 0,\n\t\t\tlen = list.length;\n\t\tfor ( ; i < len; i++ ) {\n\t\t\tif ( list[i] === elem ) {\n\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\t\treturn -1;\n\t},\n\n\tbooleans = \"checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped\",\n\n\t// Regular expressions\n\n\t// Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace\n\twhitespace = \"[\\\\x20\\\\t\\\\r\\\\n\\\\f]\",\n\t// http://www.w3.org/TR/css3-syntax/#characters\n\tcharacterEncoding = \"(?:\\\\\\\\.|[\\\\w-]|[^\\\\x00-\\\\xa0])+\",\n\n\t// Loosely modeled on CSS identifier characters\n\t// An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors\n\t// Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier\n\tidentifier = characterEncoding.replace( \"w\", \"w#\" ),\n\n\t// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors\n\tattributes = \"\\\\[\" + whitespace + \"*(\" + characterEncoding + \")(?:\" + whitespace +\n\t\t// Operator (capture 2)\n\t\t\"*([*^$|!~]?=)\" + whitespace +\n\t\t// \"Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]\"\n\t\t\"*(?:'((?:\\\\\\\\.|[^\\\\\\\\'])*)'|\\\"((?:\\\\\\\\.|[^\\\\\\\\\\\"])*)\\\"|(\" + identifier + \"))|)\" + whitespace +\n\t\t\"*\\\\]\",\n\n\tpseudos = \":(\" + characterEncoding + \")(?:\\\\((\" +\n\t\t// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:\n\t\t// 1. quoted (capture 3; capture 4 or capture 5)\n\t\t\"('((?:\\\\\\\\.|[^\\\\\\\\'])*)'|\\\"((?:\\\\\\\\.|[^\\\\\\\\\\\"])*)\\\")|\" +\n\t\t// 2. simple (capture 6)\n\t\t\"((?:\\\\\\\\.|[^\\\\\\\\()[\\\\]]|\" + attributes + \")*)|\" +\n\t\t// 3. anything else (capture 2)\n\t\t\".*\" +\n\t\t\")\\\\)|)\",\n\n\t// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter\n\trwhitespace = new RegExp( whitespace + \"+\", \"g\" ),\n\trtrim = new RegExp( \"^\" + whitespace + \"+|((?:^|[^\\\\\\\\])(?:\\\\\\\\.)*)\" + whitespace + \"+$\", \"g\" ),\n\n\trcomma = new RegExp( \"^\" + whitespace + \"*,\" + whitespace + \"*\" ),\n\trcombinators = new RegExp( \"^\" + whitespace + \"*([>+~]|\" + whitespace + \")\" + whitespace + \"*\" ),\n\n\trattributeQuotes = new RegExp( \"=\" + whitespace + \"*([^\\\\]'\\\"]*?)\" + whitespace + \"*\\\\]\", \"g\" ),\n\n\trpseudo = new RegExp( pseudos ),\n\tridentifier = new RegExp( \"^\" + identifier + \"$\" ),\n\n\tmatchExpr = {\n\t\t\"ID\": new RegExp( \"^#(\" + characterEncoding + \")\" ),\n\t\t\"CLASS\": new RegExp( \"^\\\\.(\" + characterEncoding + \")\" ),\n\t\t\"TAG\": new RegExp( \"^(\" + characterEncoding.replace( \"w\", \"w*\" ) + \")\" ),\n\t\t\"ATTR\": new RegExp( \"^\" + attributes ),\n\t\t\"PSEUDO\": new RegExp( \"^\" + pseudos ),\n\t\t\"CHILD\": new RegExp( \"^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\\\(\" + whitespace +\n\t\t\t\"*(even|odd|(([+-]|)(\\\\d*)n|)\" + whitespace + \"*(?:([+-]|)\" + whitespace +\n\t\t\t\"*(\\\\d+)|))\" + whitespace + \"*\\\\)|)\", \"i\" ),\n\t\t\"bool\": new RegExp( \"^(?:\" + booleans + \")$\", \"i\" ),\n\t\t// For use in libraries implementing .is()\n\t\t// We use this for POS matching in `select`\n\t\t\"needsContext\": new RegExp( \"^\" + whitespace + \"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\\\(\" +\n\t\t\twhitespace + \"*((?:-\\\\d)?\\\\d*)\" + whitespace + \"*\\\\)|)(?=[^-]|$)\", \"i\" )\n\t},\n\n\trinputs = /^(?:input|select|textarea|button)$/i,\n\trheader = /^h\\d$/i,\n\n\trnative = /^[^{]+\\{\\s*\\[native \\w/,\n\n\t// Easily-parseable/retrievable ID or TAG or CLASS selectors\n\trquickExpr = /^(?:#([\\w-]+)|(\\w+)|\\.([\\w-]+))$/,\n\n\trsibling = /[+~]/,\n\trescape = /'|\\\\/g,\n\n\t// CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters\n\trunescape = new RegExp( \"\\\\\\\\([\\\\da-f]{1,6}\" + whitespace + \"?|(\" + whitespace + \")|.)\", \"ig\" ),\n\tfunescape = function( _, escaped, escapedWhitespace ) {\n\t\tvar high = \"0x\" + escaped - 0x10000;\n\t\t// NaN means non-codepoint\n\t\t// Support: Firefox<24\n\t\t// Workaround erroneous numeric interpretation of +\"0x\"\n\t\treturn high !== high || escapedWhitespace ?\n\t\t\tescaped :\n\t\t\thigh < 0 ?\n\t\t\t\t// BMP codepoint\n\t\t\t\tString.fromCharCode( high + 0x10000 ) :\n\t\t\t\t// Supplemental Plane codepoint (surrogate pair)\n\t\t\t\tString.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );\n\t},\n\n\t// Used for iframes\n\t// See setDocument()\n\t// Removing the function wrapper causes a \"Permission Denied\"\n\t// error in IE\n\tunloadHandler = function() {\n\t\tsetDocument();\n\t};\n\n// Optimize for push.apply( _, NodeList )\ntry {\n\tpush.apply(\n\t\t(arr = slice.call( preferredDoc.childNodes )),\n\t\tpreferredDoc.childNodes\n\t);\n\t// Support: Android<4.0\n\t// Detect silently failing push.apply\n\tarr[ preferredDoc.childNodes.length ].nodeType;\n} catch ( e ) {\n\tpush = { apply: arr.length ?\n\n\t\t// Leverage slice if possible\n\t\tfunction( target, els ) {\n\t\t\tpush_native.apply( target, slice.call(els) );\n\t\t} :\n\n\t\t// Support: IE<9\n\t\t// Otherwise append directly\n\t\tfunction( target, els ) {\n\t\t\tvar j = target.length,\n\t\t\t\ti = 0;\n\t\t\t// Can't trust NodeList.length\n\t\t\twhile ( (target[j++] = els[i++]) ) {}\n\t\t\ttarget.length = j - 1;\n\t\t}\n\t};\n}\n\nfunction Sizzle( selector, context, results, seed ) {\n\tvar match, elem, m, nodeType,\n\t\t// QSA vars\n\t\ti, groups, old, nid, newContext, newSelector;\n\n\tif ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {\n\t\tsetDocument( context );\n\t}\n\n\tcontext = context || document;\n\tresults = results || [];\n\tnodeType = context.nodeType;\n\n\tif ( typeof selector !== \"string\" || !selector ||\n\t\tnodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {\n\n\t\treturn results;\n\t}\n\n\tif ( !seed && documentIsHTML ) {\n\n\t\t// Try to shortcut find operations when possible (e.g., not under DocumentFragment)\n\t\tif ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) {\n\t\t\t// Speed-up: Sizzle(\"#ID\")\n\t\t\tif ( (m = match[1]) ) {\n\t\t\t\tif ( nodeType === 9 ) {\n\t\t\t\t\telem = context.getElementById( m );\n\t\t\t\t\t// Check parentNode to catch when Blackberry 4.6 returns\n\t\t\t\t\t// nodes that are no longer in the document (jQuery #6963)\n\t\t\t\t\tif ( elem && elem.parentNode ) {\n\t\t\t\t\t\t// Handle the case where IE, Opera, and Webkit return items\n\t\t\t\t\t\t// by name instead of ID\n\t\t\t\t\t\tif ( elem.id === m ) {\n\t\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\t\treturn results;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn results;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Context is not a document\n\t\t\t\t\tif ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&\n\t\t\t\t\t\tcontains( context, elem ) && elem.id === m ) {\n\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\treturn results;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t// Speed-up: Sizzle(\"TAG\")\n\t\t\t} else if ( match[2] ) {\n\t\t\t\tpush.apply( results, context.getElementsByTagName( selector ) );\n\t\t\t\treturn results;\n\n\t\t\t// Speed-up: Sizzle(\".CLASS\")\n\t\t\t} else if ( (m = match[3]) && support.getElementsByClassName ) {\n\t\t\t\tpush.apply( results, context.getElementsByClassName( m ) );\n\t\t\t\treturn results;\n\t\t\t}\n\t\t}\n\n\t\t// QSA path\n\t\tif ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {\n\t\t\tnid = old = expando;\n\t\t\tnewContext = context;\n\t\t\tnewSelector = nodeType !== 1 && selector;\n\n\t\t\t// qSA works strangely on Element-rooted queries\n\t\t\t// We can work around this by specifying an extra ID on the root\n\t\t\t// and working up from there (Thanks to Andrew Dupont for the technique)\n\t\t\t// IE 8 doesn't work on object elements\n\t\t\tif ( nodeType === 1 && context.nodeName.toLowerCase() !== \"object\" ) {\n\t\t\t\tgroups = tokenize( selector );\n\n\t\t\t\tif ( (old = context.getAttribute(\"id\")) ) {\n\t\t\t\t\tnid = old.replace( rescape, \"\\\\$&\" );\n\t\t\t\t} else {\n\t\t\t\t\tcontext.setAttribute( \"id\", nid );\n\t\t\t\t}\n\t\t\t\tnid = \"[id='\" + nid + \"'] \";\n\n\t\t\t\ti = groups.length;\n\t\t\t\twhile ( i-- ) {\n\t\t\t\t\tgroups[i] = nid + toSelector( groups[i] );\n\t\t\t\t}\n\t\t\t\tnewContext = rsibling.test( selector ) && testContext( context.parentNode ) || context;\n\t\t\t\tnewSelector = groups.join(\",\");\n\t\t\t}\n\n\t\t\tif ( newSelector ) {\n\t\t\t\ttry {\n\t\t\t\t\tpush.apply( results,\n\t\t\t\t\t\tnewContext.querySelectorAll( newSelector )\n\t\t\t\t\t);\n\t\t\t\t\treturn results;\n\t\t\t\t} catch(qsaError) {\n\t\t\t\t} finally {\n\t\t\t\t\tif ( !old ) {\n\t\t\t\t\t\tcontext.removeAttribute(\"id\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// All others\n\treturn select( selector.replace( rtrim, \"$1\" ), context, results, seed );\n}\n\n/**\n * Create key-value caches of limited size\n * @returns {Function(string, Object)} Returns the Object data after storing it on itself with\n *\tproperty name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)\n *\tdeleting the oldest entry\n */\nfunction createCache() {\n\tvar keys = [];\n\n\tfunction cache( key, value ) {\n\t\t// Use (key + \" \") to avoid collision with native prototype properties (see Issue #157)\n\t\tif ( keys.push( key + \" \" ) > Expr.cacheLength ) {\n\t\t\t// Only keep the most recent entries\n\t\t\tdelete cache[ keys.shift() ];\n\t\t}\n\t\treturn (cache[ key + \" \" ] = value);\n\t}\n\treturn cache;\n}\n\n/**\n * Mark a function for special use by Sizzle\n * @param {Function} fn The function to mark\n */\nfunction markFunction( fn ) {\n\tfn[ expando ] = true;\n\treturn fn;\n}\n\n/**\n * Support testing using an element\n * @param {Function} fn Passed the created div and expects a boolean result\n */\nfunction assert( fn ) {\n\tvar div = document.createElement(\"div\");\n\n\ttry {\n\t\treturn !!fn( div );\n\t} catch (e) {\n\t\treturn false;\n\t} finally {\n\t\t// Remove from its parent by default\n\t\tif ( div.parentNode ) {\n\t\t\tdiv.parentNode.removeChild( div );\n\t\t}\n\t\t// release memory in IE\n\t\tdiv = null;\n\t}\n}\n\n/**\n * Adds the same handler for all of the specified attrs\n * @param {String} attrs Pipe-separated list of attributes\n * @param {Function} handler The method that will be applied\n */\nfunction addHandle( attrs, handler ) {\n\tvar arr = attrs.split(\"|\"),\n\t\ti = attrs.length;\n\n\twhile ( i-- ) {\n\t\tExpr.attrHandle[ arr[i] ] = handler;\n\t}\n}\n\n/**\n * Checks document order of two siblings\n * @param {Element} a\n * @param {Element} b\n * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b\n */\nfunction siblingCheck( a, b ) {\n\tvar cur = b && a,\n\t\tdiff = cur && a.nodeType === 1 && b.nodeType === 1 &&\n\t\t\t( ~b.sourceIndex || MAX_NEGATIVE ) -\n\t\t\t( ~a.sourceIndex || MAX_NEGATIVE );\n\n\t// Use IE sourceIndex if available on both nodes\n\tif ( diff ) {\n\t\treturn diff;\n\t}\n\n\t// Check if b follows a\n\tif ( cur ) {\n\t\twhile ( (cur = cur.nextSibling) ) {\n\t\t\tif ( cur === b ) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn a ? 1 : -1;\n}\n\n/**\n * Returns a function to use in pseudos for input types\n * @param {String} type\n */\nfunction createInputPseudo( type ) {\n\treturn function( elem ) {\n\t\tvar name = elem.nodeName.toLowerCase();\n\t\treturn name === \"input\" && elem.type === type;\n\t};\n}\n\n/**\n * Returns a function to use in pseudos for buttons\n * @param {String} type\n */\nfunction createButtonPseudo( type ) {\n\treturn function( elem ) {\n\t\tvar name = elem.nodeName.toLowerCase();\n\t\treturn (name === \"input\" || name === \"button\") && elem.type === type;\n\t};\n}\n\n/**\n * Returns a function to use in pseudos for positionals\n * @param {Function} fn\n */\nfunction createPositionalPseudo( fn ) {\n\treturn markFunction(function( argument ) {\n\t\targument = +argument;\n\t\treturn markFunction(function( seed, matches ) {\n\t\t\tvar j,\n\t\t\t\tmatchIndexes = fn( [], seed.length, argument ),\n\t\t\t\ti = matchIndexes.length;\n\n\t\t\t// Match elements found at the specified indexes\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( seed[ (j = matchIndexes[i]) ] ) {\n\t\t\t\t\tseed[j] = !(matches[j] = seed[j]);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t});\n}\n\n/**\n * Checks a node for validity as a Sizzle context\n * @param {Element|Object=} context\n * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value\n */\nfunction testContext( context ) {\n\treturn context && typeof context.getElementsByTagName !== \"undefined\" && context;\n}\n\n// Expose support vars for convenience\nsupport = Sizzle.support = {};\n\n/**\n * Detects XML nodes\n * @param {Element|Object} elem An element or a document\n * @returns {Boolean} True iff elem is a non-HTML XML node\n */\nisXML = Sizzle.isXML = function( elem ) {\n\t// documentElement is verified for cases where it doesn't yet exist\n\t// (such as loading iframes in IE - #4833)\n\tvar documentElement = elem && (elem.ownerDocument || elem).documentElement;\n\treturn documentElement ? documentElement.nodeName !== \"HTML\" : false;\n};\n\n/**\n * Sets document-related variables once based on the current document\n * @param {Element|Object} [doc] An element or document object to use to set the document\n * @returns {Object} Returns the current document\n */\nsetDocument = Sizzle.setDocument = function( node ) {\n\tvar hasCompare, parent,\n\t\tdoc = node ? node.ownerDocument || node : preferredDoc;\n\n\t// If no document and documentElement is available, return\n\tif ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {\n\t\treturn document;\n\t}\n\n\t// Set our document\n\tdocument = doc;\n\tdocElem = doc.documentElement;\n\tparent = doc.defaultView;\n\n\t// Support: IE>8\n\t// If iframe document is assigned to \"document\" variable and if iframe has been reloaded,\n\t// IE will throw \"permission denied\" error when accessing \"document\" variable, see jQuery #13936\n\t// IE6-8 do not support the defaultView property so parent will be undefined\n\tif ( parent && parent !== parent.top ) {\n\t\t// IE11 does not have attachEvent, so all must suffer\n\t\tif ( parent.addEventListener ) {\n\t\t\tparent.addEventListener( \"unload\", unloadHandler, false );\n\t\t} else if ( parent.attachEvent ) {\n\t\t\tparent.attachEvent( \"onunload\", unloadHandler );\n\t\t}\n\t}\n\n\t/* Support tests\n\t---------------------------------------------------------------------- */\n\tdocumentIsHTML = !isXML( doc );\n\n\t/* Attributes\n\t---------------------------------------------------------------------- */\n\n\t// Support: IE<8\n\t// Verify that getAttribute really returns attributes and not properties\n\t// (excepting IE8 booleans)\n\tsupport.attributes = assert(function( div ) {\n\t\tdiv.className = \"i\";\n\t\treturn !div.getAttribute(\"className\");\n\t});\n\n\t/* getElement(s)By*\n\t---------------------------------------------------------------------- */\n\n\t// Check if getElementsByTagName(\"*\") returns only elements\n\tsupport.getElementsByTagName = assert(function( div ) {\n\t\tdiv.appendChild( doc.createComment(\"\") );\n\t\treturn !div.getElementsByTagName(\"*\").length;\n\t});\n\n\t// Support: IE<9\n\tsupport.getElementsByClassName = rnative.test( doc.getElementsByClassName );\n\n\t// Support: IE<10\n\t// Check if getElementById returns elements by name\n\t// The broken getElementById methods don't pick up programatically-set names,\n\t// so use a roundabout getElementsByName test\n\tsupport.getById = assert(function( div ) {\n\t\tdocElem.appendChild( div ).id = expando;\n\t\treturn !doc.getElementsByName || !doc.getElementsByName( expando ).length;\n\t});\n\n\t// ID find and filter\n\tif ( support.getById ) {\n\t\tExpr.find[\"ID\"] = function( id, context ) {\n\t\t\tif ( typeof context.getElementById !== \"undefined\" && documentIsHTML ) {\n\t\t\t\tvar m = context.getElementById( id );\n\t\t\t\t// Check parentNode to catch when Blackberry 4.6 returns\n\t\t\t\t// nodes that are no longer in the document #6963\n\t\t\t\treturn m && m.parentNode ? [ m ] : [];\n\t\t\t}\n\t\t};\n\t\tExpr.filter[\"ID\"] = function( id ) {\n\t\t\tvar attrId = id.replace( runescape, funescape );\n\t\t\treturn function( elem ) {\n\t\t\t\treturn elem.getAttribute(\"id\") === attrId;\n\t\t\t};\n\t\t};\n\t} else {\n\t\t// Support: IE6/7\n\t\t// getElementById is not reliable as a find shortcut\n\t\tdelete Expr.find[\"ID\"];\n\n\t\tExpr.filter[\"ID\"] =  function( id ) {\n\t\t\tvar attrId = id.replace( runescape, funescape );\n\t\t\treturn function( elem ) {\n\t\t\t\tvar node = typeof elem.getAttributeNode !== \"undefined\" && elem.getAttributeNode(\"id\");\n\t\t\t\treturn node && node.value === attrId;\n\t\t\t};\n\t\t};\n\t}\n\n\t// Tag\n\tExpr.find[\"TAG\"] = support.getElementsByTagName ?\n\t\tfunction( tag, context ) {\n\t\t\tif ( typeof context.getElementsByTagName !== \"undefined\" ) {\n\t\t\t\treturn context.getElementsByTagName( tag );\n\n\t\t\t// DocumentFragment nodes don't have gEBTN\n\t\t\t} else if ( support.qsa ) {\n\t\t\t\treturn context.querySelectorAll( tag );\n\t\t\t}\n\t\t} :\n\n\t\tfunction( tag, context ) {\n\t\t\tvar elem,\n\t\t\t\ttmp = [],\n\t\t\t\ti = 0,\n\t\t\t\t// By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too\n\t\t\t\tresults = context.getElementsByTagName( tag );\n\n\t\t\t// Filter out possible comments\n\t\t\tif ( tag === \"*\" ) {\n\t\t\t\twhile ( (elem = results[i++]) ) {\n\t\t\t\t\tif ( elem.nodeType === 1 ) {\n\t\t\t\t\t\ttmp.push( elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn tmp;\n\t\t\t}\n\t\t\treturn results;\n\t\t};\n\n\t// Class\n\tExpr.find[\"CLASS\"] = support.getElementsByClassName && function( className, context ) {\n\t\tif ( documentIsHTML ) {\n\t\t\treturn context.getElementsByClassName( className );\n\t\t}\n\t};\n\n\t/* QSA/matchesSelector\n\t---------------------------------------------------------------------- */\n\n\t// QSA and matchesSelector support\n\n\t// matchesSelector(:active) reports false when true (IE9/Opera 11.5)\n\trbuggyMatches = [];\n\n\t// qSa(:focus) reports false when true (Chrome 21)\n\t// We allow this because of a bug in IE8/9 that throws an error\n\t// whenever `document.activeElement` is accessed on an iframe\n\t// So, we allow :focus to pass through QSA all the time to avoid the IE error\n\t// See http://bugs.jquery.com/ticket/13378\n\trbuggyQSA = [];\n\n\tif ( (support.qsa = rnative.test( doc.querySelectorAll )) ) {\n\t\t// Build QSA regex\n\t\t// Regex strategy adopted from Diego Perini\n\t\tassert(function( div ) {\n\t\t\t// Select is set to empty string on purpose\n\t\t\t// This is to test IE's treatment of not explicitly\n\t\t\t// setting a boolean content attribute,\n\t\t\t// since its presence should be enough\n\t\t\t// http://bugs.jquery.com/ticket/12359\n\t\t\tdocElem.appendChild( div ).innerHTML = \"<a id='\" + expando + \"'></a>\" +\n\t\t\t\t\"<select id='\" + expando + \"-\\f]' msallowcapture=''>\" +\n\t\t\t\t\"<option selected=''></option></select>\";\n\n\t\t\t// Support: IE8, Opera 11-12.16\n\t\t\t// Nothing should be selected when empty strings follow ^= or $= or *=\n\t\t\t// The test attribute must be unknown in Opera but \"safe\" for WinRT\n\t\t\t// http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section\n\t\t\tif ( div.querySelectorAll(\"[msallowcapture^='']\").length ) {\n\t\t\t\trbuggyQSA.push( \"[*^$]=\" + whitespace + \"*(?:''|\\\"\\\")\" );\n\t\t\t}\n\n\t\t\t// Support: IE8\n\t\t\t// Boolean attributes and \"value\" are not treated correctly\n\t\t\tif ( !div.querySelectorAll(\"[selected]\").length ) {\n\t\t\t\trbuggyQSA.push( \"\\\\[\" + whitespace + \"*(?:value|\" + booleans + \")\" );\n\t\t\t}\n\n\t\t\t// Support: Chrome<29, Android<4.2+, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.7+\n\t\t\tif ( !div.querySelectorAll( \"[id~=\" + expando + \"-]\" ).length ) {\n\t\t\t\trbuggyQSA.push(\"~=\");\n\t\t\t}\n\n\t\t\t// Webkit/Opera - :checked should return selected option elements\n\t\t\t// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\n\t\t\t// IE8 throws error here and will not see later tests\n\t\t\tif ( !div.querySelectorAll(\":checked\").length ) {\n\t\t\t\trbuggyQSA.push(\":checked\");\n\t\t\t}\n\n\t\t\t// Support: Safari 8+, iOS 8+\n\t\t\t// https://bugs.webkit.org/show_bug.cgi?id=136851\n\t\t\t// In-page `selector#id sibing-combinator selector` fails\n\t\t\tif ( !div.querySelectorAll( \"a#\" + expando + \"+*\" ).length ) {\n\t\t\t\trbuggyQSA.push(\".#.+[+~]\");\n\t\t\t}\n\t\t});\n\n\t\tassert(function( div ) {\n\t\t\t// Support: Windows 8 Native Apps\n\t\t\t// The type and name attributes are restricted during .innerHTML assignment\n\t\t\tvar input = doc.createElement(\"input\");\n\t\t\tinput.setAttribute( \"type\", \"hidden\" );\n\t\t\tdiv.appendChild( input ).setAttribute( \"name\", \"D\" );\n\n\t\t\t// Support: IE8\n\t\t\t// Enforce case-sensitivity of name attribute\n\t\t\tif ( div.querySelectorAll(\"[name=d]\").length ) {\n\t\t\t\trbuggyQSA.push( \"name\" + whitespace + \"*[*^$|!~]?=\" );\n\t\t\t}\n\n\t\t\t// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)\n\t\t\t// IE8 throws error here and will not see later tests\n\t\t\tif ( !div.querySelectorAll(\":enabled\").length ) {\n\t\t\t\trbuggyQSA.push( \":enabled\", \":disabled\" );\n\t\t\t}\n\n\t\t\t// Opera 10-11 does not throw on post-comma invalid pseudos\n\t\t\tdiv.querySelectorAll(\"*,:x\");\n\t\t\trbuggyQSA.push(\",.*:\");\n\t\t});\n\t}\n\n\tif ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||\n\t\tdocElem.webkitMatchesSelector ||\n\t\tdocElem.mozMatchesSelector ||\n\t\tdocElem.oMatchesSelector ||\n\t\tdocElem.msMatchesSelector) )) ) {\n\n\t\tassert(function( div ) {\n\t\t\t// Check to see if it's possible to do matchesSelector\n\t\t\t// on a disconnected node (IE 9)\n\t\t\tsupport.disconnectedMatch = matches.call( div, \"div\" );\n\n\t\t\t// This should fail with an exception\n\t\t\t// Gecko does not error, returns false instead\n\t\t\tmatches.call( div, \"[s!='']:x\" );\n\t\t\trbuggyMatches.push( \"!=\", pseudos );\n\t\t});\n\t}\n\n\trbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join(\"|\") );\n\trbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join(\"|\") );\n\n\t/* Contains\n\t---------------------------------------------------------------------- */\n\thasCompare = rnative.test( docElem.compareDocumentPosition );\n\n\t// Element contains another\n\t// Purposefully does not implement inclusive descendent\n\t// As in, an element does not contain itself\n\tcontains = hasCompare || rnative.test( docElem.contains ) ?\n\t\tfunction( a, b ) {\n\t\t\tvar adown = a.nodeType === 9 ? a.documentElement : a,\n\t\t\t\tbup = b && b.parentNode;\n\t\t\treturn a === bup || !!( bup && bup.nodeType === 1 && (\n\t\t\t\tadown.contains ?\n\t\t\t\t\tadown.contains( bup ) :\n\t\t\t\t\ta.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16\n\t\t\t));\n\t\t} :\n\t\tfunction( a, b ) {\n\t\t\tif ( b ) {\n\t\t\t\twhile ( (b = b.parentNode) ) {\n\t\t\t\t\tif ( b === a ) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false;\n\t\t};\n\n\t/* Sorting\n\t---------------------------------------------------------------------- */\n\n\t// Document order sorting\n\tsortOrder = hasCompare ?\n\tfunction( a, b ) {\n\n\t\t// Flag for duplicate removal\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t\treturn 0;\n\t\t}\n\n\t\t// Sort on method existence if only one input has compareDocumentPosition\n\t\tvar compare = !a.compareDocumentPosition - !b.compareDocumentPosition;\n\t\tif ( compare ) {\n\t\t\treturn compare;\n\t\t}\n\n\t\t// Calculate position if both inputs belong to the same document\n\t\tcompare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?\n\t\t\ta.compareDocumentPosition( b ) :\n\n\t\t\t// Otherwise we know they are disconnected\n\t\t\t1;\n\n\t\t// Disconnected nodes\n\t\tif ( compare & 1 ||\n\t\t\t(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {\n\n\t\t\t// Choose the first element that is related to our preferred document\n\t\t\tif ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tif ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {\n\t\t\t\treturn 1;\n\t\t\t}\n\n\t\t\t// Maintain original order\n\t\t\treturn sortInput ?\n\t\t\t\t( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :\n\t\t\t\t0;\n\t\t}\n\n\t\treturn compare & 4 ? -1 : 1;\n\t} :\n\tfunction( a, b ) {\n\t\t// Exit early if the nodes are identical\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t\treturn 0;\n\t\t}\n\n\t\tvar cur,\n\t\t\ti = 0,\n\t\t\taup = a.parentNode,\n\t\t\tbup = b.parentNode,\n\t\t\tap = [ a ],\n\t\t\tbp = [ b ];\n\n\t\t// Parentless nodes are either documents or disconnected\n\t\tif ( !aup || !bup ) {\n\t\t\treturn a === doc ? -1 :\n\t\t\t\tb === doc ? 1 :\n\t\t\t\taup ? -1 :\n\t\t\t\tbup ? 1 :\n\t\t\t\tsortInput ?\n\t\t\t\t( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :\n\t\t\t\t0;\n\n\t\t// If the nodes are siblings, we can do a quick check\n\t\t} else if ( aup === bup ) {\n\t\t\treturn siblingCheck( a, b );\n\t\t}\n\n\t\t// Otherwise we need full lists of their ancestors for comparison\n\t\tcur = a;\n\t\twhile ( (cur = cur.parentNode) ) {\n\t\t\tap.unshift( cur );\n\t\t}\n\t\tcur = b;\n\t\twhile ( (cur = cur.parentNode) ) {\n\t\t\tbp.unshift( cur );\n\t\t}\n\n\t\t// Walk down the tree looking for a discrepancy\n\t\twhile ( ap[i] === bp[i] ) {\n\t\t\ti++;\n\t\t}\n\n\t\treturn i ?\n\t\t\t// Do a sibling check if the nodes have a common ancestor\n\t\t\tsiblingCheck( ap[i], bp[i] ) :\n\n\t\t\t// Otherwise nodes in our document sort first\n\t\t\tap[i] === preferredDoc ? -1 :\n\t\t\tbp[i] === preferredDoc ? 1 :\n\t\t\t0;\n\t};\n\n\treturn doc;\n};\n\nSizzle.matches = function( expr, elements ) {\n\treturn Sizzle( expr, null, null, elements );\n};\n\nSizzle.matchesSelector = function( elem, expr ) {\n\t// Set document vars if needed\n\tif ( ( elem.ownerDocument || elem ) !== document ) {\n\t\tsetDocument( elem );\n\t}\n\n\t// Make sure that attribute selectors are quoted\n\texpr = expr.replace( rattributeQuotes, \"='$1']\" );\n\n\tif ( support.matchesSelector && documentIsHTML &&\n\t\t( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&\n\t\t( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {\n\n\t\ttry {\n\t\t\tvar ret = matches.call( elem, expr );\n\n\t\t\t// IE 9's matchesSelector returns false on disconnected nodes\n\t\t\tif ( ret || support.disconnectedMatch ||\n\t\t\t\t\t// As well, disconnected nodes are said to be in a document\n\t\t\t\t\t// fragment in IE 9\n\t\t\t\t\telem.document && elem.document.nodeType !== 11 ) {\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t} catch (e) {}\n\t}\n\n\treturn Sizzle( expr, document, null, [ elem ] ).length > 0;\n};\n\nSizzle.contains = function( context, elem ) {\n\t// Set document vars if needed\n\tif ( ( context.ownerDocument || context ) !== document ) {\n\t\tsetDocument( context );\n\t}\n\treturn contains( context, elem );\n};\n\nSizzle.attr = function( elem, name ) {\n\t// Set document vars if needed\n\tif ( ( elem.ownerDocument || elem ) !== document ) {\n\t\tsetDocument( elem );\n\t}\n\n\tvar fn = Expr.attrHandle[ name.toLowerCase() ],\n\t\t// Don't get fooled by Object.prototype properties (jQuery #13807)\n\t\tval = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?\n\t\t\tfn( elem, name, !documentIsHTML ) :\n\t\t\tundefined;\n\n\treturn val !== undefined ?\n\t\tval :\n\t\tsupport.attributes || !documentIsHTML ?\n\t\t\telem.getAttribute( name ) :\n\t\t\t(val = elem.getAttributeNode(name)) && val.specified ?\n\t\t\t\tval.value :\n\t\t\t\tnull;\n};\n\nSizzle.error = function( msg ) {\n\tthrow new Error( \"Syntax error, unrecognized expression: \" + msg );\n};\n\n/**\n * Document sorting and removing duplicates\n * @param {ArrayLike} results\n */\nSizzle.uniqueSort = function( results ) {\n\tvar elem,\n\t\tduplicates = [],\n\t\tj = 0,\n\t\ti = 0;\n\n\t// Unless we *know* we can detect duplicates, assume their presence\n\thasDuplicate = !support.detectDuplicates;\n\tsortInput = !support.sortStable && results.slice( 0 );\n\tresults.sort( sortOrder );\n\n\tif ( hasDuplicate ) {\n\t\twhile ( (elem = results[i++]) ) {\n\t\t\tif ( elem === results[ i ] ) {\n\t\t\t\tj = duplicates.push( i );\n\t\t\t}\n\t\t}\n\t\twhile ( j-- ) {\n\t\t\tresults.splice( duplicates[ j ], 1 );\n\t\t}\n\t}\n\n\t// Clear input after sorting to release objects\n\t// See https://github.com/jquery/sizzle/pull/225\n\tsortInput = null;\n\n\treturn results;\n};\n\n/**\n * Utility function for retrieving the text value of an array of DOM nodes\n * @param {Array|Element} elem\n */\ngetText = Sizzle.getText = function( elem ) {\n\tvar node,\n\t\tret = \"\",\n\t\ti = 0,\n\t\tnodeType = elem.nodeType;\n\n\tif ( !nodeType ) {\n\t\t// If no nodeType, this is expected to be an array\n\t\twhile ( (node = elem[i++]) ) {\n\t\t\t// Do not traverse comment nodes\n\t\t\tret += getText( node );\n\t\t}\n\t} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {\n\t\t// Use textContent for elements\n\t\t// innerText usage removed for consistency of new lines (jQuery #11153)\n\t\tif ( typeof elem.textContent === \"string\" ) {\n\t\t\treturn elem.textContent;\n\t\t} else {\n\t\t\t// Traverse its children\n\t\t\tfor ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {\n\t\t\t\tret += getText( elem );\n\t\t\t}\n\t\t}\n\t} else if ( nodeType === 3 || nodeType === 4 ) {\n\t\treturn elem.nodeValue;\n\t}\n\t// Do not include comment or processing instruction nodes\n\n\treturn ret;\n};\n\nExpr = Sizzle.selectors = {\n\n\t// Can be adjusted by the user\n\tcacheLength: 50,\n\n\tcreatePseudo: markFunction,\n\n\tmatch: matchExpr,\n\n\tattrHandle: {},\n\n\tfind: {},\n\n\trelative: {\n\t\t\">\": { dir: \"parentNode\", first: true },\n\t\t\" \": { dir: \"parentNode\" },\n\t\t\"+\": { dir: \"previousSibling\", first: true },\n\t\t\"~\": { dir: \"previousSibling\" }\n\t},\n\n\tpreFilter: {\n\t\t\"ATTR\": function( match ) {\n\t\t\tmatch[1] = match[1].replace( runescape, funescape );\n\n\t\t\t// Move the given value to match[3] whether quoted or unquoted\n\t\t\tmatch[3] = ( match[3] || match[4] || match[5] || \"\" ).replace( runescape, funescape );\n\n\t\t\tif ( match[2] === \"~=\" ) {\n\t\t\t\tmatch[3] = \" \" + match[3] + \" \";\n\t\t\t}\n\n\t\t\treturn match.slice( 0, 4 );\n\t\t},\n\n\t\t\"CHILD\": function( match ) {\n\t\t\t/* matches from matchExpr[\"CHILD\"]\n\t\t\t\t1 type (only|nth|...)\n\t\t\t\t2 what (child|of-type)\n\t\t\t\t3 argument (even|odd|\\d*|\\d*n([+-]\\d+)?|...)\n\t\t\t\t4 xn-component of xn+y argument ([+-]?\\d*n|)\n\t\t\t\t5 sign of xn-component\n\t\t\t\t6 x of xn-component\n\t\t\t\t7 sign of y-component\n\t\t\t\t8 y of y-component\n\t\t\t*/\n\t\t\tmatch[1] = match[1].toLowerCase();\n\n\t\t\tif ( match[1].slice( 0, 3 ) === \"nth\" ) {\n\t\t\t\t// nth-* requires argument\n\t\t\t\tif ( !match[3] ) {\n\t\t\t\t\tSizzle.error( match[0] );\n\t\t\t\t}\n\n\t\t\t\t// numeric x and y parameters for Expr.filter.CHILD\n\t\t\t\t// remember that false/true cast respectively to 0/1\n\t\t\t\tmatch[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === \"even\" || match[3] === \"odd\" ) );\n\t\t\t\tmatch[5] = +( ( match[7] + match[8] ) || match[3] === \"odd\" );\n\n\t\t\t// other types prohibit arguments\n\t\t\t} else if ( match[3] ) {\n\t\t\t\tSizzle.error( match[0] );\n\t\t\t}\n\n\t\t\treturn match;\n\t\t},\n\n\t\t\"PSEUDO\": function( match ) {\n\t\t\tvar excess,\n\t\t\t\tunquoted = !match[6] && match[2];\n\n\t\t\tif ( matchExpr[\"CHILD\"].test( match[0] ) ) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Accept quoted arguments as-is\n\t\t\tif ( match[3] ) {\n\t\t\t\tmatch[2] = match[4] || match[5] || \"\";\n\n\t\t\t// Strip excess characters from unquoted arguments\n\t\t\t} else if ( unquoted && rpseudo.test( unquoted ) &&\n\t\t\t\t// Get excess from tokenize (recursively)\n\t\t\t\t(excess = tokenize( unquoted, true )) &&\n\t\t\t\t// advance to the next closing parenthesis\n\t\t\t\t(excess = unquoted.indexOf( \")\", unquoted.length - excess ) - unquoted.length) ) {\n\n\t\t\t\t// excess is a negative index\n\t\t\t\tmatch[0] = match[0].slice( 0, excess );\n\t\t\t\tmatch[2] = unquoted.slice( 0, excess );\n\t\t\t}\n\n\t\t\t// Return only captures needed by the pseudo filter method (type and argument)\n\t\t\treturn match.slice( 0, 3 );\n\t\t}\n\t},\n\n\tfilter: {\n\n\t\t\"TAG\": function( nodeNameSelector ) {\n\t\t\tvar nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();\n\t\t\treturn nodeNameSelector === \"*\" ?\n\t\t\t\tfunction() { return true; } :\n\t\t\t\tfunction( elem ) {\n\t\t\t\t\treturn elem.nodeName && elem.nodeName.toLowerCase() === nodeName;\n\t\t\t\t};\n\t\t},\n\n\t\t\"CLASS\": function( className ) {\n\t\t\tvar pattern = classCache[ className + \" \" ];\n\n\t\t\treturn pattern ||\n\t\t\t\t(pattern = new RegExp( \"(^|\" + whitespace + \")\" + className + \"(\" + whitespace + \"|$)\" )) &&\n\t\t\t\tclassCache( className, function( elem ) {\n\t\t\t\t\treturn pattern.test( typeof elem.className === \"string\" && elem.className || typeof elem.getAttribute !== \"undefined\" && elem.getAttribute(\"class\") || \"\" );\n\t\t\t\t});\n\t\t},\n\n\t\t\"ATTR\": function( name, operator, check ) {\n\t\t\treturn function( elem ) {\n\t\t\t\tvar result = Sizzle.attr( elem, name );\n\n\t\t\t\tif ( result == null ) {\n\t\t\t\t\treturn operator === \"!=\";\n\t\t\t\t}\n\t\t\t\tif ( !operator ) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\tresult += \"\";\n\n\t\t\t\treturn operator === \"=\" ? result === check :\n\t\t\t\t\toperator === \"!=\" ? result !== check :\n\t\t\t\t\toperator === \"^=\" ? check && result.indexOf( check ) === 0 :\n\t\t\t\t\toperator === \"*=\" ? check && result.indexOf( check ) > -1 :\n\t\t\t\t\toperator === \"$=\" ? check && result.slice( -check.length ) === check :\n\t\t\t\t\toperator === \"~=\" ? ( \" \" + result.replace( rwhitespace, \" \" ) + \" \" ).indexOf( check ) > -1 :\n\t\t\t\t\toperator === \"|=\" ? result === check || result.slice( 0, check.length + 1 ) === check + \"-\" :\n\t\t\t\t\tfalse;\n\t\t\t};\n\t\t},\n\n\t\t\"CHILD\": function( type, what, argument, first, last ) {\n\t\t\tvar simple = type.slice( 0, 3 ) !== \"nth\",\n\t\t\t\tforward = type.slice( -4 ) !== \"last\",\n\t\t\t\tofType = what === \"of-type\";\n\n\t\t\treturn first === 1 && last === 0 ?\n\n\t\t\t\t// Shortcut for :nth-*(n)\n\t\t\t\tfunction( elem ) {\n\t\t\t\t\treturn !!elem.parentNode;\n\t\t\t\t} :\n\n\t\t\t\tfunction( elem, context, xml ) {\n\t\t\t\t\tvar cache, outerCache, node, diff, nodeIndex, start,\n\t\t\t\t\t\tdir = simple !== forward ? \"nextSibling\" : \"previousSibling\",\n\t\t\t\t\t\tparent = elem.parentNode,\n\t\t\t\t\t\tname = ofType && elem.nodeName.toLowerCase(),\n\t\t\t\t\t\tuseCache = !xml && !ofType;\n\n\t\t\t\t\tif ( parent ) {\n\n\t\t\t\t\t\t// :(first|last|only)-(child|of-type)\n\t\t\t\t\t\tif ( simple ) {\n\t\t\t\t\t\t\twhile ( dir ) {\n\t\t\t\t\t\t\t\tnode = elem;\n\t\t\t\t\t\t\t\twhile ( (node = node[ dir ]) ) {\n\t\t\t\t\t\t\t\t\tif ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {\n\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// Reverse direction for :only-* (if we haven't yet done so)\n\t\t\t\t\t\t\t\tstart = dir = type === \"only\" && !start && \"nextSibling\";\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tstart = [ forward ? parent.firstChild : parent.lastChild ];\n\n\t\t\t\t\t\t// non-xml :nth-child(...) stores cache data on `parent`\n\t\t\t\t\t\tif ( forward && useCache ) {\n\t\t\t\t\t\t\t// Seek `elem` from a previously-cached index\n\t\t\t\t\t\t\touterCache = parent[ expando ] || (parent[ expando ] = {});\n\t\t\t\t\t\t\tcache = outerCache[ type ] || [];\n\t\t\t\t\t\t\tnodeIndex = cache[0] === dirruns && cache[1];\n\t\t\t\t\t\t\tdiff = cache[0] === dirruns && cache[2];\n\t\t\t\t\t\t\tnode = nodeIndex && parent.childNodes[ nodeIndex ];\n\n\t\t\t\t\t\t\twhile ( (node = ++nodeIndex && node && node[ dir ] ||\n\n\t\t\t\t\t\t\t\t// Fallback to seeking `elem` from the start\n\t\t\t\t\t\t\t\t(diff = nodeIndex = 0) || start.pop()) ) {\n\n\t\t\t\t\t\t\t\t// When found, cache indexes on `parent` and break\n\t\t\t\t\t\t\t\tif ( node.nodeType === 1 && ++diff && node === elem ) {\n\t\t\t\t\t\t\t\t\touterCache[ type ] = [ dirruns, nodeIndex, diff ];\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Use previously-cached element index if available\n\t\t\t\t\t\t} else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {\n\t\t\t\t\t\t\tdiff = cache[1];\n\n\t\t\t\t\t\t// xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Use the same loop as above to seek `elem` from the start\n\t\t\t\t\t\t\twhile ( (node = ++nodeIndex && node && node[ dir ] ||\n\t\t\t\t\t\t\t\t(diff = nodeIndex = 0) || start.pop()) ) {\n\n\t\t\t\t\t\t\t\tif ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {\n\t\t\t\t\t\t\t\t\t// Cache the index of each encountered element\n\t\t\t\t\t\t\t\t\tif ( useCache ) {\n\t\t\t\t\t\t\t\t\t\t(node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\tif ( node === elem ) {\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Incorporate the offset, then check against cycle size\n\t\t\t\t\t\tdiff -= last;\n\t\t\t\t\t\treturn diff === first || ( diff % first === 0 && diff / first >= 0 );\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t},\n\n\t\t\"PSEUDO\": function( pseudo, argument ) {\n\t\t\t// pseudo-class names are case-insensitive\n\t\t\t// http://www.w3.org/TR/selectors/#pseudo-classes\n\t\t\t// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters\n\t\t\t// Remember that setFilters inherits from pseudos\n\t\t\tvar args,\n\t\t\t\tfn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||\n\t\t\t\t\tSizzle.error( \"unsupported pseudo: \" + pseudo );\n\n\t\t\t// The user may use createPseudo to indicate that\n\t\t\t// arguments are needed to create the filter function\n\t\t\t// just as Sizzle does\n\t\t\tif ( fn[ expando ] ) {\n\t\t\t\treturn fn( argument );\n\t\t\t}\n\n\t\t\t// But maintain support for old signatures\n\t\t\tif ( fn.length > 1 ) {\n\t\t\t\targs = [ pseudo, pseudo, \"\", argument ];\n\t\t\t\treturn Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?\n\t\t\t\t\tmarkFunction(function( seed, matches ) {\n\t\t\t\t\t\tvar idx,\n\t\t\t\t\t\t\tmatched = fn( seed, argument ),\n\t\t\t\t\t\t\ti = matched.length;\n\t\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\t\tidx = indexOf( seed, matched[i] );\n\t\t\t\t\t\t\tseed[ idx ] = !( matches[ idx ] = matched[i] );\n\t\t\t\t\t\t}\n\t\t\t\t\t}) :\n\t\t\t\t\tfunction( elem ) {\n\t\t\t\t\t\treturn fn( elem, 0, args );\n\t\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn fn;\n\t\t}\n\t},\n\n\tpseudos: {\n\t\t// Potentially complex pseudos\n\t\t\"not\": markFunction(function( selector ) {\n\t\t\t// Trim the selector passed to compile\n\t\t\t// to avoid treating leading and trailing\n\t\t\t// spaces as combinators\n\t\t\tvar input = [],\n\t\t\t\tresults = [],\n\t\t\t\tmatcher = compile( selector.replace( rtrim, \"$1\" ) );\n\n\t\t\treturn matcher[ expando ] ?\n\t\t\t\tmarkFunction(function( seed, matches, context, xml ) {\n\t\t\t\t\tvar elem,\n\t\t\t\t\t\tunmatched = matcher( seed, null, xml, [] ),\n\t\t\t\t\t\ti = seed.length;\n\n\t\t\t\t\t// Match elements unmatched by `matcher`\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\tif ( (elem = unmatched[i]) ) {\n\t\t\t\t\t\t\tseed[i] = !(matches[i] = elem);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}) :\n\t\t\t\tfunction( elem, context, xml ) {\n\t\t\t\t\tinput[0] = elem;\n\t\t\t\t\tmatcher( input, null, xml, results );\n\t\t\t\t\t// Don't keep the element (issue #299)\n\t\t\t\t\tinput[0] = null;\n\t\t\t\t\treturn !results.pop();\n\t\t\t\t};\n\t\t}),\n\n\t\t\"has\": markFunction(function( selector ) {\n\t\t\treturn function( elem ) {\n\t\t\t\treturn Sizzle( selector, elem ).length > 0;\n\t\t\t};\n\t\t}),\n\n\t\t\"contains\": markFunction(function( text ) {\n\t\t\ttext = text.replace( runescape, funescape );\n\t\t\treturn function( elem ) {\n\t\t\t\treturn ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;\n\t\t\t};\n\t\t}),\n\n\t\t// \"Whether an element is represented by a :lang() selector\n\t\t// is based solely on the element's language value\n\t\t// being equal to the identifier C,\n\t\t// or beginning with the identifier C immediately followed by \"-\".\n\t\t// The matching of C against the element's language value is performed case-insensitively.\n\t\t// The identifier C does not have to be a valid language name.\"\n\t\t// http://www.w3.org/TR/selectors/#lang-pseudo\n\t\t\"lang\": markFunction( function( lang ) {\n\t\t\t// lang value must be a valid identifier\n\t\t\tif ( !ridentifier.test(lang || \"\") ) {\n\t\t\t\tSizzle.error( \"unsupported lang: \" + lang );\n\t\t\t}\n\t\t\tlang = lang.replace( runescape, funescape ).toLowerCase();\n\t\t\treturn function( elem ) {\n\t\t\t\tvar elemLang;\n\t\t\t\tdo {\n\t\t\t\t\tif ( (elemLang = documentIsHTML ?\n\t\t\t\t\t\telem.lang :\n\t\t\t\t\t\telem.getAttribute(\"xml:lang\") || elem.getAttribute(\"lang\")) ) {\n\n\t\t\t\t\t\telemLang = elemLang.toLowerCase();\n\t\t\t\t\t\treturn elemLang === lang || elemLang.indexOf( lang + \"-\" ) === 0;\n\t\t\t\t\t}\n\t\t\t\t} while ( (elem = elem.parentNode) && elem.nodeType === 1 );\n\t\t\t\treturn false;\n\t\t\t};\n\t\t}),\n\n\t\t// Miscellaneous\n\t\t\"target\": function( elem ) {\n\t\t\tvar hash = window.location && window.location.hash;\n\t\t\treturn hash && hash.slice( 1 ) === elem.id;\n\t\t},\n\n\t\t\"root\": function( elem ) {\n\t\t\treturn elem === docElem;\n\t\t},\n\n\t\t\"focus\": function( elem ) {\n\t\t\treturn elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);\n\t\t},\n\n\t\t// Boolean properties\n\t\t\"enabled\": function( elem ) {\n\t\t\treturn elem.disabled === false;\n\t\t},\n\n\t\t\"disabled\": function( elem ) {\n\t\t\treturn elem.disabled === true;\n\t\t},\n\n\t\t\"checked\": function( elem ) {\n\t\t\t// In CSS3, :checked should return both checked and selected elements\n\t\t\t// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\n\t\t\tvar nodeName = elem.nodeName.toLowerCase();\n\t\t\treturn (nodeName === \"input\" && !!elem.checked) || (nodeName === \"option\" && !!elem.selected);\n\t\t},\n\n\t\t\"selected\": function( elem ) {\n\t\t\t// Accessing this property makes selected-by-default\n\t\t\t// options in Safari work properly\n\t\t\tif ( elem.parentNode ) {\n\t\t\t\telem.parentNode.selectedIndex;\n\t\t\t}\n\n\t\t\treturn elem.selected === true;\n\t\t},\n\n\t\t// Contents\n\t\t\"empty\": function( elem ) {\n\t\t\t// http://www.w3.org/TR/selectors/#empty-pseudo\n\t\t\t// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),\n\t\t\t//   but not by others (comment: 8; processing instruction: 7; etc.)\n\t\t\t// nodeType < 6 works because attributes (2) do not appear as children\n\t\t\tfor ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {\n\t\t\t\tif ( elem.nodeType < 6 ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t},\n\n\t\t\"parent\": function( elem ) {\n\t\t\treturn !Expr.pseudos[\"empty\"]( elem );\n\t\t},\n\n\t\t// Element/input types\n\t\t\"header\": function( elem ) {\n\t\t\treturn rheader.test( elem.nodeName );\n\t\t},\n\n\t\t\"input\": function( elem ) {\n\t\t\treturn rinputs.test( elem.nodeName );\n\t\t},\n\n\t\t\"button\": function( elem ) {\n\t\t\tvar name = elem.nodeName.toLowerCase();\n\t\t\treturn name === \"input\" && elem.type === \"button\" || name === \"button\";\n\t\t},\n\n\t\t\"text\": function( elem ) {\n\t\t\tvar attr;\n\t\t\treturn elem.nodeName.toLowerCase() === \"input\" &&\n\t\t\t\telem.type === \"text\" &&\n\n\t\t\t\t// Support: IE<8\n\t\t\t\t// New HTML5 attribute values (e.g., \"search\") appear with elem.type === \"text\"\n\t\t\t\t( (attr = elem.getAttribute(\"type\")) == null || attr.toLowerCase() === \"text\" );\n\t\t},\n\n\t\t// Position-in-collection\n\t\t\"first\": createPositionalPseudo(function() {\n\t\t\treturn [ 0 ];\n\t\t}),\n\n\t\t\"last\": createPositionalPseudo(function( matchIndexes, length ) {\n\t\t\treturn [ length - 1 ];\n\t\t}),\n\n\t\t\"eq\": createPositionalPseudo(function( matchIndexes, length, argument ) {\n\t\t\treturn [ argument < 0 ? argument + length : argument ];\n\t\t}),\n\n\t\t\"even\": createPositionalPseudo(function( matchIndexes, length ) {\n\t\t\tvar i = 0;\n\t\t\tfor ( ; i < length; i += 2 ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t}),\n\n\t\t\"odd\": createPositionalPseudo(function( matchIndexes, length ) {\n\t\t\tvar i = 1;\n\t\t\tfor ( ; i < length; i += 2 ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t}),\n\n\t\t\"lt\": createPositionalPseudo(function( matchIndexes, length, argument ) {\n\t\t\tvar i = argument < 0 ? argument + length : argument;\n\t\t\tfor ( ; --i >= 0; ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t}),\n\n\t\t\"gt\": createPositionalPseudo(function( matchIndexes, length, argument ) {\n\t\t\tvar i = argument < 0 ? argument + length : argument;\n\t\t\tfor ( ; ++i < length; ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t})\n\t}\n};\n\nExpr.pseudos[\"nth\"] = Expr.pseudos[\"eq\"];\n\n// Add button/input type pseudos\nfor ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {\n\tExpr.pseudos[ i ] = createInputPseudo( i );\n}\nfor ( i in { submit: true, reset: true } ) {\n\tExpr.pseudos[ i ] = createButtonPseudo( i );\n}\n\n// Easy API for creating new setFilters\nfunction setFilters() {}\nsetFilters.prototype = Expr.filters = Expr.pseudos;\nExpr.setFilters = new setFilters();\n\ntokenize = Sizzle.tokenize = function( selector, parseOnly ) {\n\tvar matched, match, tokens, type,\n\t\tsoFar, groups, preFilters,\n\t\tcached = tokenCache[ selector + \" \" ];\n\n\tif ( cached ) {\n\t\treturn parseOnly ? 0 : cached.slice( 0 );\n\t}\n\n\tsoFar = selector;\n\tgroups = [];\n\tpreFilters = Expr.preFilter;\n\n\twhile ( soFar ) {\n\n\t\t// Comma and first run\n\t\tif ( !matched || (match = rcomma.exec( soFar )) ) {\n\t\t\tif ( match ) {\n\t\t\t\t// Don't consume trailing commas as valid\n\t\t\t\tsoFar = soFar.slice( match[0].length ) || soFar;\n\t\t\t}\n\t\t\tgroups.push( (tokens = []) );\n\t\t}\n\n\t\tmatched = false;\n\n\t\t// Combinators\n\t\tif ( (match = rcombinators.exec( soFar )) ) {\n\t\t\tmatched = match.shift();\n\t\t\ttokens.push({\n\t\t\t\tvalue: matched,\n\t\t\t\t// Cast descendant combinators to space\n\t\t\t\ttype: match[0].replace( rtrim, \" \" )\n\t\t\t});\n\t\t\tsoFar = soFar.slice( matched.length );\n\t\t}\n\n\t\t// Filters\n\t\tfor ( type in Expr.filter ) {\n\t\t\tif ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||\n\t\t\t\t(match = preFilters[ type ]( match ))) ) {\n\t\t\t\tmatched = match.shift();\n\t\t\t\ttokens.push({\n\t\t\t\t\tvalue: matched,\n\t\t\t\t\ttype: type,\n\t\t\t\t\tmatches: match\n\t\t\t\t});\n\t\t\t\tsoFar = soFar.slice( matched.length );\n\t\t\t}\n\t\t}\n\n\t\tif ( !matched ) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Return the length of the invalid excess\n\t// if we're just parsing\n\t// Otherwise, throw an error or return tokens\n\treturn parseOnly ?\n\t\tsoFar.length :\n\t\tsoFar ?\n\t\t\tSizzle.error( selector ) :\n\t\t\t// Cache the tokens\n\t\t\ttokenCache( selector, groups ).slice( 0 );\n};\n\nfunction toSelector( tokens ) {\n\tvar i = 0,\n\t\tlen = tokens.length,\n\t\tselector = \"\";\n\tfor ( ; i < len; i++ ) {\n\t\tselector += tokens[i].value;\n\t}\n\treturn selector;\n}\n\nfunction addCombinator( matcher, combinator, base ) {\n\tvar dir = combinator.dir,\n\t\tcheckNonElements = base && dir === \"parentNode\",\n\t\tdoneName = done++;\n\n\treturn combinator.first ?\n\t\t// Check against closest ancestor/preceding element\n\t\tfunction( elem, context, xml ) {\n\t\t\twhile ( (elem = elem[ dir ]) ) {\n\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\n\t\t\t\t\treturn matcher( elem, context, xml );\n\t\t\t\t}\n\t\t\t}\n\t\t} :\n\n\t\t// Check against all ancestor/preceding elements\n\t\tfunction( elem, context, xml ) {\n\t\t\tvar oldCache, outerCache,\n\t\t\t\tnewCache = [ dirruns, doneName ];\n\n\t\t\t// We can't set arbitrary data on XML nodes, so they don't benefit from dir caching\n\t\t\tif ( xml ) {\n\t\t\t\twhile ( (elem = elem[ dir ]) ) {\n\t\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\n\t\t\t\t\t\tif ( matcher( elem, context, xml ) ) {\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\twhile ( (elem = elem[ dir ]) ) {\n\t\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\n\t\t\t\t\t\touterCache = elem[ expando ] || (elem[ expando ] = {});\n\t\t\t\t\t\tif ( (oldCache = outerCache[ dir ]) &&\n\t\t\t\t\t\t\toldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {\n\n\t\t\t\t\t\t\t// Assign to newCache so results back-propagate to previous elements\n\t\t\t\t\t\t\treturn (newCache[ 2 ] = oldCache[ 2 ]);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Reuse newcache so results back-propagate to previous elements\n\t\t\t\t\t\t\touterCache[ dir ] = newCache;\n\n\t\t\t\t\t\t\t// A match means we're done; a fail means we have to keep checking\n\t\t\t\t\t\t\tif ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {\n\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t};\n}\n\nfunction elementMatcher( matchers ) {\n\treturn matchers.length > 1 ?\n\t\tfunction( elem, context, xml ) {\n\t\t\tvar i = matchers.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( !matchers[i]( elem, context, xml ) ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t} :\n\t\tmatchers[0];\n}\n\nfunction multipleContexts( selector, contexts, results ) {\n\tvar i = 0,\n\t\tlen = contexts.length;\n\tfor ( ; i < len; i++ ) {\n\t\tSizzle( selector, contexts[i], results );\n\t}\n\treturn results;\n}\n\nfunction condense( unmatched, map, filter, context, xml ) {\n\tvar elem,\n\t\tnewUnmatched = [],\n\t\ti = 0,\n\t\tlen = unmatched.length,\n\t\tmapped = map != null;\n\n\tfor ( ; i < len; i++ ) {\n\t\tif ( (elem = unmatched[i]) ) {\n\t\t\tif ( !filter || filter( elem, context, xml ) ) {\n\t\t\t\tnewUnmatched.push( elem );\n\t\t\t\tif ( mapped ) {\n\t\t\t\t\tmap.push( i );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn newUnmatched;\n}\n\nfunction setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {\n\tif ( postFilter && !postFilter[ expando ] ) {\n\t\tpostFilter = setMatcher( postFilter );\n\t}\n\tif ( postFinder && !postFinder[ expando ] ) {\n\t\tpostFinder = setMatcher( postFinder, postSelector );\n\t}\n\treturn markFunction(function( seed, results, context, xml ) {\n\t\tvar temp, i, elem,\n\t\t\tpreMap = [],\n\t\t\tpostMap = [],\n\t\t\tpreexisting = results.length,\n\n\t\t\t// Get initial elements from seed or context\n\t\t\telems = seed || multipleContexts( selector || \"*\", context.nodeType ? [ context ] : context, [] ),\n\n\t\t\t// Prefilter to get matcher input, preserving a map for seed-results synchronization\n\t\t\tmatcherIn = preFilter && ( seed || !selector ) ?\n\t\t\t\tcondense( elems, preMap, preFilter, context, xml ) :\n\t\t\t\telems,\n\n\t\t\tmatcherOut = matcher ?\n\t\t\t\t// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,\n\t\t\t\tpostFinder || ( seed ? preFilter : preexisting || postFilter ) ?\n\n\t\t\t\t\t// ...intermediate processing is necessary\n\t\t\t\t\t[] :\n\n\t\t\t\t\t// ...otherwise use results directly\n\t\t\t\t\tresults :\n\t\t\t\tmatcherIn;\n\n\t\t// Find primary matches\n\t\tif ( matcher ) {\n\t\t\tmatcher( matcherIn, matcherOut, context, xml );\n\t\t}\n\n\t\t// Apply postFilter\n\t\tif ( postFilter ) {\n\t\t\ttemp = condense( matcherOut, postMap );\n\t\t\tpostFilter( temp, [], context, xml );\n\n\t\t\t// Un-match failing elements by moving them back to matcherIn\n\t\t\ti = temp.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( (elem = temp[i]) ) {\n\t\t\t\t\tmatcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif ( seed ) {\n\t\t\tif ( postFinder || preFilter ) {\n\t\t\t\tif ( postFinder ) {\n\t\t\t\t\t// Get the final matcherOut by condensing this intermediate into postFinder contexts\n\t\t\t\t\ttemp = [];\n\t\t\t\t\ti = matcherOut.length;\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\tif ( (elem = matcherOut[i]) ) {\n\t\t\t\t\t\t\t// Restore matcherIn since elem is not yet a final match\n\t\t\t\t\t\t\ttemp.push( (matcherIn[i] = elem) );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tpostFinder( null, (matcherOut = []), temp, xml );\n\t\t\t\t}\n\n\t\t\t\t// Move matched elements from seed to results to keep them synchronized\n\t\t\t\ti = matcherOut.length;\n\t\t\t\twhile ( i-- ) {\n\t\t\t\t\tif ( (elem = matcherOut[i]) &&\n\t\t\t\t\t\t(temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) {\n\n\t\t\t\t\t\tseed[temp] = !(results[temp] = elem);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t// Add elements to results, through postFinder if defined\n\t\t} else {\n\t\t\tmatcherOut = condense(\n\t\t\t\tmatcherOut === results ?\n\t\t\t\t\tmatcherOut.splice( preexisting, matcherOut.length ) :\n\t\t\t\t\tmatcherOut\n\t\t\t);\n\t\t\tif ( postFinder ) {\n\t\t\t\tpostFinder( null, results, matcherOut, xml );\n\t\t\t} else {\n\t\t\t\tpush.apply( results, matcherOut );\n\t\t\t}\n\t\t}\n\t});\n}\n\nfunction matcherFromTokens( tokens ) {\n\tvar checkContext, matcher, j,\n\t\tlen = tokens.length,\n\t\tleadingRelative = Expr.relative[ tokens[0].type ],\n\t\timplicitRelative = leadingRelative || Expr.relative[\" \"],\n\t\ti = leadingRelative ? 1 : 0,\n\n\t\t// The foundational matcher ensures that elements are reachable from top-level context(s)\n\t\tmatchContext = addCombinator( function( elem ) {\n\t\t\treturn elem === checkContext;\n\t\t}, implicitRelative, true ),\n\t\tmatchAnyContext = addCombinator( function( elem ) {\n\t\t\treturn indexOf( checkContext, elem ) > -1;\n\t\t}, implicitRelative, true ),\n\t\tmatchers = [ function( elem, context, xml ) {\n\t\t\tvar ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (\n\t\t\t\t(checkContext = context).nodeType ?\n\t\t\t\t\tmatchContext( elem, context, xml ) :\n\t\t\t\t\tmatchAnyContext( elem, context, xml ) );\n\t\t\t// Avoid hanging onto element (issue #299)\n\t\t\tcheckContext = null;\n\t\t\treturn ret;\n\t\t} ];\n\n\tfor ( ; i < len; i++ ) {\n\t\tif ( (matcher = Expr.relative[ tokens[i].type ]) ) {\n\t\t\tmatchers = [ addCombinator(elementMatcher( matchers ), matcher) ];\n\t\t} else {\n\t\t\tmatcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );\n\n\t\t\t// Return special upon seeing a positional matcher\n\t\t\tif ( matcher[ expando ] ) {\n\t\t\t\t// Find the next relative operator (if any) for proper handling\n\t\t\t\tj = ++i;\n\t\t\t\tfor ( ; j < len; j++ ) {\n\t\t\t\t\tif ( Expr.relative[ tokens[j].type ] ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn setMatcher(\n\t\t\t\t\ti > 1 && elementMatcher( matchers ),\n\t\t\t\t\ti > 1 && toSelector(\n\t\t\t\t\t\t// If the preceding token was a descendant combinator, insert an implicit any-element `*`\n\t\t\t\t\t\ttokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === \" \" ? \"*\" : \"\" })\n\t\t\t\t\t).replace( rtrim, \"$1\" ),\n\t\t\t\t\tmatcher,\n\t\t\t\t\ti < j && matcherFromTokens( tokens.slice( i, j ) ),\n\t\t\t\t\tj < len && matcherFromTokens( (tokens = tokens.slice( j )) ),\n\t\t\t\t\tj < len && toSelector( tokens )\n\t\t\t\t);\n\t\t\t}\n\t\t\tmatchers.push( matcher );\n\t\t}\n\t}\n\n\treturn elementMatcher( matchers );\n}\n\nfunction matcherFromGroupMatchers( elementMatchers, setMatchers ) {\n\tvar bySet = setMatchers.length > 0,\n\t\tbyElement = elementMatchers.length > 0,\n\t\tsuperMatcher = function( seed, context, xml, results, outermost ) {\n\t\t\tvar elem, j, matcher,\n\t\t\t\tmatchedCount = 0,\n\t\t\t\ti = \"0\",\n\t\t\t\tunmatched = seed && [],\n\t\t\t\tsetMatched = [],\n\t\t\t\tcontextBackup = outermostContext,\n\t\t\t\t// We must always have either seed elements or outermost context\n\t\t\t\telems = seed || byElement && Expr.find[\"TAG\"]( \"*\", outermost ),\n\t\t\t\t// Use integer dirruns iff this is the outermost matcher\n\t\t\t\tdirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),\n\t\t\t\tlen = elems.length;\n\n\t\t\tif ( outermost ) {\n\t\t\t\toutermostContext = context !== document && context;\n\t\t\t}\n\n\t\t\t// Add elements passing elementMatchers directly to results\n\t\t\t// Keep `i` a string if there are no elements so `matchedCount` will be \"00\" below\n\t\t\t// Support: IE<9, Safari\n\t\t\t// Tolerate NodeList properties (IE: \"length\"; Safari: <number>) matching elements by id\n\t\t\tfor ( ; i !== len && (elem = elems[i]) != null; i++ ) {\n\t\t\t\tif ( byElement && elem ) {\n\t\t\t\t\tj = 0;\n\t\t\t\t\twhile ( (matcher = elementMatchers[j++]) ) {\n\t\t\t\t\t\tif ( matcher( elem, context, xml ) ) {\n\t\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif ( outermost ) {\n\t\t\t\t\t\tdirruns = dirrunsUnique;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Track unmatched elements for set filters\n\t\t\t\tif ( bySet ) {\n\t\t\t\t\t// They will have gone through all possible matchers\n\t\t\t\t\tif ( (elem = !matcher && elem) ) {\n\t\t\t\t\t\tmatchedCount--;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Lengthen the array for every element, matched or not\n\t\t\t\t\tif ( seed ) {\n\t\t\t\t\t\tunmatched.push( elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Apply set filters to unmatched elements\n\t\t\tmatchedCount += i;\n\t\t\tif ( bySet && i !== matchedCount ) {\n\t\t\t\tj = 0;\n\t\t\t\twhile ( (matcher = setMatchers[j++]) ) {\n\t\t\t\t\tmatcher( unmatched, setMatched, context, xml );\n\t\t\t\t}\n\n\t\t\t\tif ( seed ) {\n\t\t\t\t\t// Reintegrate element matches to eliminate the need for sorting\n\t\t\t\t\tif ( matchedCount > 0 ) {\n\t\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\t\tif ( !(unmatched[i] || setMatched[i]) ) {\n\t\t\t\t\t\t\t\tsetMatched[i] = pop.call( results );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Discard index placeholder values to get only actual matches\n\t\t\t\t\tsetMatched = condense( setMatched );\n\t\t\t\t}\n\n\t\t\t\t// Add matches to results\n\t\t\t\tpush.apply( results, setMatched );\n\n\t\t\t\t// Seedless set matches succeeding multiple successful matchers stipulate sorting\n\t\t\t\tif ( outermost && !seed && setMatched.length > 0 &&\n\t\t\t\t\t( matchedCount + setMatchers.length ) > 1 ) {\n\n\t\t\t\t\tSizzle.uniqueSort( results );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Override manipulation of globals by nested matchers\n\t\t\tif ( outermost ) {\n\t\t\t\tdirruns = dirrunsUnique;\n\t\t\t\toutermostContext = contextBackup;\n\t\t\t}\n\n\t\t\treturn unmatched;\n\t\t};\n\n\treturn bySet ?\n\t\tmarkFunction( superMatcher ) :\n\t\tsuperMatcher;\n}\n\ncompile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {\n\tvar i,\n\t\tsetMatchers = [],\n\t\telementMatchers = [],\n\t\tcached = compilerCache[ selector + \" \" ];\n\n\tif ( !cached ) {\n\t\t// Generate a function of recursive functions that can be used to check each element\n\t\tif ( !match ) {\n\t\t\tmatch = tokenize( selector );\n\t\t}\n\t\ti = match.length;\n\t\twhile ( i-- ) {\n\t\t\tcached = matcherFromTokens( match[i] );\n\t\t\tif ( cached[ expando ] ) {\n\t\t\t\tsetMatchers.push( cached );\n\t\t\t} else {\n\t\t\t\telementMatchers.push( cached );\n\t\t\t}\n\t\t}\n\n\t\t// Cache the compiled function\n\t\tcached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );\n\n\t\t// Save selector and tokenization\n\t\tcached.selector = selector;\n\t}\n\treturn cached;\n};\n\n/**\n * A low-level selection function that works with Sizzle's compiled\n *  selector functions\n * @param {String|Function} selector A selector or a pre-compiled\n *  selector function built with Sizzle.compile\n * @param {Element} context\n * @param {Array} [results]\n * @param {Array} [seed] A set of elements to match against\n */\nselect = Sizzle.select = function( selector, context, results, seed ) {\n\tvar i, tokens, token, type, find,\n\t\tcompiled = typeof selector === \"function\" && selector,\n\t\tmatch = !seed && tokenize( (selector = compiled.selector || selector) );\n\n\tresults = results || [];\n\n\t// Try to minimize operations if there is no seed and only one group\n\tif ( match.length === 1 ) {\n\n\t\t// Take a shortcut and set the context if the root selector is an ID\n\t\ttokens = match[0] = match[0].slice( 0 );\n\t\tif ( tokens.length > 2 && (token = tokens[0]).type === \"ID\" &&\n\t\t\t\tsupport.getById && context.nodeType === 9 && documentIsHTML &&\n\t\t\t\tExpr.relative[ tokens[1].type ] ) {\n\n\t\t\tcontext = ( Expr.find[\"ID\"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];\n\t\t\tif ( !context ) {\n\t\t\t\treturn results;\n\n\t\t\t// Precompiled matchers will still verify ancestry, so step up a level\n\t\t\t} else if ( compiled ) {\n\t\t\t\tcontext = context.parentNode;\n\t\t\t}\n\n\t\t\tselector = selector.slice( tokens.shift().value.length );\n\t\t}\n\n\t\t// Fetch a seed set for right-to-left matching\n\t\ti = matchExpr[\"needsContext\"].test( selector ) ? 0 : tokens.length;\n\t\twhile ( i-- ) {\n\t\t\ttoken = tokens[i];\n\n\t\t\t// Abort if we hit a combinator\n\t\t\tif ( Expr.relative[ (type = token.type) ] ) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif ( (find = Expr.find[ type ]) ) {\n\t\t\t\t// Search, expanding context for leading sibling combinators\n\t\t\t\tif ( (seed = find(\n\t\t\t\t\ttoken.matches[0].replace( runescape, funescape ),\n\t\t\t\t\trsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context\n\t\t\t\t)) ) {\n\n\t\t\t\t\t// If seed is empty or no tokens remain, we can return early\n\t\t\t\t\ttokens.splice( i, 1 );\n\t\t\t\t\tselector = seed.length && toSelector( tokens );\n\t\t\t\t\tif ( !selector ) {\n\t\t\t\t\t\tpush.apply( results, seed );\n\t\t\t\t\t\treturn results;\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Compile and execute a filtering function if one is not provided\n\t// Provide `match` to avoid retokenization if we modified the selector above\n\t( compiled || compile( selector, match ) )(\n\t\tseed,\n\t\tcontext,\n\t\t!documentIsHTML,\n\t\tresults,\n\t\trsibling.test( selector ) && testContext( context.parentNode ) || context\n\t);\n\treturn results;\n};\n\n// One-time assignments\n\n// Sort stability\nsupport.sortStable = expando.split(\"\").sort( sortOrder ).join(\"\") === expando;\n\n// Support: Chrome 14-35+\n// Always assume duplicates if they aren't passed to the comparison function\nsupport.detectDuplicates = !!hasDuplicate;\n\n// Initialize against the default document\nsetDocument();\n\n// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)\n// Detached nodes confoundingly follow *each other*\nsupport.sortDetached = assert(function( div1 ) {\n\t// Should return 1, but returns 4 (following)\n\treturn div1.compareDocumentPosition( document.createElement(\"div\") ) & 1;\n});\n\n// Support: IE<8\n// Prevent attribute/property \"interpolation\"\n// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx\nif ( !assert(function( div ) {\n\tdiv.innerHTML = \"<a href='#'></a>\";\n\treturn div.firstChild.getAttribute(\"href\") === \"#\" ;\n}) ) {\n\taddHandle( \"type|href|height|width\", function( elem, name, isXML ) {\n\t\tif ( !isXML ) {\n\t\t\treturn elem.getAttribute( name, name.toLowerCase() === \"type\" ? 1 : 2 );\n\t\t}\n\t});\n}\n\n// Support: IE<9\n// Use defaultValue in place of getAttribute(\"value\")\nif ( !support.attributes || !assert(function( div ) {\n\tdiv.innerHTML = \"<input/>\";\n\tdiv.firstChild.setAttribute( \"value\", \"\" );\n\treturn div.firstChild.getAttribute( \"value\" ) === \"\";\n}) ) {\n\taddHandle( \"value\", function( elem, name, isXML ) {\n\t\tif ( !isXML && elem.nodeName.toLowerCase() === \"input\" ) {\n\t\t\treturn elem.defaultValue;\n\t\t}\n\t});\n}\n\n// Support: IE<9\n// Use getAttributeNode to fetch booleans when getAttribute lies\nif ( !assert(function( div ) {\n\treturn div.getAttribute(\"disabled\") == null;\n}) ) {\n\taddHandle( booleans, function( elem, name, isXML ) {\n\t\tvar val;\n\t\tif ( !isXML ) {\n\t\t\treturn elem[ name ] === true ? name.toLowerCase() :\n\t\t\t\t\t(val = elem.getAttributeNode( name )) && val.specified ?\n\t\t\t\t\tval.value :\n\t\t\t\tnull;\n\t\t}\n\t});\n}\n\nreturn Sizzle;\n\n})( window );\n\n\n\njQuery.find = Sizzle;\njQuery.expr = Sizzle.selectors;\njQuery.expr[\":\"] = jQuery.expr.pseudos;\njQuery.unique = Sizzle.uniqueSort;\njQuery.text = Sizzle.getText;\njQuery.isXMLDoc = Sizzle.isXML;\njQuery.contains = Sizzle.contains;\n\n\n\nvar rneedsContext = jQuery.expr.match.needsContext;\n\nvar rsingleTag = (/^<(\\w+)\\s*\\/?>(?:<\\/\\1>|)$/);\n\n\n\nvar risSimple = /^.[^:#\\[\\.,]*$/;\n\n// Implement the identical functionality for filter and not\nfunction winnow( elements, qualifier, not ) {\n\tif ( jQuery.isFunction( qualifier ) ) {\n\t\treturn jQuery.grep( elements, function( elem, i ) {\n\t\t\t/* jshint -W018 */\n\t\t\treturn !!qualifier.call( elem, i, elem ) !== not;\n\t\t});\n\n\t}\n\n\tif ( qualifier.nodeType ) {\n\t\treturn jQuery.grep( elements, function( elem ) {\n\t\t\treturn ( elem === qualifier ) !== not;\n\t\t});\n\n\t}\n\n\tif ( typeof qualifier === \"string\" ) {\n\t\tif ( risSimple.test( qualifier ) ) {\n\t\t\treturn jQuery.filter( qualifier, elements, not );\n\t\t}\n\n\t\tqualifier = jQuery.filter( qualifier, elements );\n\t}\n\n\treturn jQuery.grep( elements, function( elem ) {\n\t\treturn ( indexOf.call( qualifier, elem ) >= 0 ) !== not;\n\t});\n}\n\njQuery.filter = function( expr, elems, not ) {\n\tvar elem = elems[ 0 ];\n\n\tif ( not ) {\n\t\texpr = \":not(\" + expr + \")\";\n\t}\n\n\treturn elems.length === 1 && elem.nodeType === 1 ?\n\t\tjQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :\n\t\tjQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {\n\t\t\treturn elem.nodeType === 1;\n\t\t}));\n};\n\njQuery.fn.extend({\n\tfind: function( selector ) {\n\t\tvar i,\n\t\t\tlen = this.length,\n\t\t\tret = [],\n\t\t\tself = this;\n\n\t\tif ( typeof selector !== \"string\" ) {\n\t\t\treturn this.pushStack( jQuery( selector ).filter(function() {\n\t\t\t\tfor ( i = 0; i < len; i++ ) {\n\t\t\t\t\tif ( jQuery.contains( self[ i ], this ) ) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}) );\n\t\t}\n\n\t\tfor ( i = 0; i < len; i++ ) {\n\t\t\tjQuery.find( selector, self[ i ], ret );\n\t\t}\n\n\t\t// Needed because $( selector, context ) becomes $( context ).find( selector )\n\t\tret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );\n\t\tret.selector = this.selector ? this.selector + \" \" + selector : selector;\n\t\treturn ret;\n\t},\n\tfilter: function( selector ) {\n\t\treturn this.pushStack( winnow(this, selector || [], false) );\n\t},\n\tnot: function( selector ) {\n\t\treturn this.pushStack( winnow(this, selector || [], true) );\n\t},\n\tis: function( selector ) {\n\t\treturn !!winnow(\n\t\t\tthis,\n\n\t\t\t// If this is a positional/relative selector, check membership in the returned set\n\t\t\t// so $(\"p:first\").is(\"p:last\") won't return true for a doc with two \"p\".\n\t\t\ttypeof selector === \"string\" && rneedsContext.test( selector ) ?\n\t\t\t\tjQuery( selector ) :\n\t\t\t\tselector || [],\n\t\t\tfalse\n\t\t).length;\n\t}\n});\n\n\n// Initialize a jQuery object\n\n\n// A central reference to the root jQuery(document)\nvar rootjQuery,\n\n\t// A simple way to check for HTML strings\n\t// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)\n\t// Strict HTML recognition (#11290: must start with <)\n\trquickExpr = /^(?:\\s*(<[\\w\\W]+>)[^>]*|#([\\w-]*))$/,\n\n\tinit = jQuery.fn.init = function( selector, context ) {\n\t\tvar match, elem;\n\n\t\t// HANDLE: $(\"\"), $(null), $(undefined), $(false)\n\t\tif ( !selector ) {\n\t\t\treturn this;\n\t\t}\n\n\t\t// Handle HTML strings\n\t\tif ( typeof selector === \"string\" ) {\n\t\t\tif ( selector[0] === \"<\" && selector[ selector.length - 1 ] === \">\" && selector.length >= 3 ) {\n\t\t\t\t// Assume that strings that start and end with <> are HTML and skip the regex check\n\t\t\t\tmatch = [ null, selector, null ];\n\n\t\t\t} else {\n\t\t\t\tmatch = rquickExpr.exec( selector );\n\t\t\t}\n\n\t\t\t// Match html or make sure no context is specified for #id\n\t\t\tif ( match && (match[1] || !context) ) {\n\n\t\t\t\t// HANDLE: $(html) -> $(array)\n\t\t\t\tif ( match[1] ) {\n\t\t\t\t\tcontext = context instanceof jQuery ? context[0] : context;\n\n\t\t\t\t\t// Option to run scripts is true for back-compat\n\t\t\t\t\t// Intentionally let the error be thrown if parseHTML is not present\n\t\t\t\t\tjQuery.merge( this, jQuery.parseHTML(\n\t\t\t\t\t\tmatch[1],\n\t\t\t\t\t\tcontext && context.nodeType ? context.ownerDocument || context : document,\n\t\t\t\t\t\ttrue\n\t\t\t\t\t) );\n\n\t\t\t\t\t// HANDLE: $(html, props)\n\t\t\t\t\tif ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {\n\t\t\t\t\t\tfor ( match in context ) {\n\t\t\t\t\t\t\t// Properties of context are called as methods if possible\n\t\t\t\t\t\t\tif ( jQuery.isFunction( this[ match ] ) ) {\n\t\t\t\t\t\t\t\tthis[ match ]( context[ match ] );\n\n\t\t\t\t\t\t\t// ...and otherwise set as attributes\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.attr( match, context[ match ] );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn this;\n\n\t\t\t\t// HANDLE: $(#id)\n\t\t\t\t} else {\n\t\t\t\t\telem = document.getElementById( match[2] );\n\n\t\t\t\t\t// Support: Blackberry 4.6\n\t\t\t\t\t// gEBID returns nodes no longer in the document (#6963)\n\t\t\t\t\tif ( elem && elem.parentNode ) {\n\t\t\t\t\t\t// Inject the element directly into the jQuery object\n\t\t\t\t\t\tthis.length = 1;\n\t\t\t\t\t\tthis[0] = elem;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.context = document;\n\t\t\t\t\tthis.selector = selector;\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\n\t\t\t// HANDLE: $(expr, $(...))\n\t\t\t} else if ( !context || context.jquery ) {\n\t\t\t\treturn ( context || rootjQuery ).find( selector );\n\n\t\t\t// HANDLE: $(expr, context)\n\t\t\t// (which is just equivalent to: $(context).find(expr)\n\t\t\t} else {\n\t\t\t\treturn this.constructor( context ).find( selector );\n\t\t\t}\n\n\t\t// HANDLE: $(DOMElement)\n\t\t} else if ( selector.nodeType ) {\n\t\t\tthis.context = this[0] = selector;\n\t\t\tthis.length = 1;\n\t\t\treturn this;\n\n\t\t// HANDLE: $(function)\n\t\t// Shortcut for document ready\n\t\t} else if ( jQuery.isFunction( selector ) ) {\n\t\t\treturn typeof rootjQuery.ready !== \"undefined\" ?\n\t\t\t\trootjQuery.ready( selector ) :\n\t\t\t\t// Execute immediately if ready is not present\n\t\t\t\tselector( jQuery );\n\t\t}\n\n\t\tif ( selector.selector !== undefined ) {\n\t\t\tthis.selector = selector.selector;\n\t\t\tthis.context = selector.context;\n\t\t}\n\n\t\treturn jQuery.makeArray( selector, this );\n\t};\n\n// Give the init function the jQuery prototype for later instantiation\ninit.prototype = jQuery.fn;\n\n// Initialize central reference\nrootjQuery = jQuery( document );\n\n\nvar rparentsprev = /^(?:parents|prev(?:Until|All))/,\n\t// Methods guaranteed to produce a unique set when starting from a unique set\n\tguaranteedUnique = {\n\t\tchildren: true,\n\t\tcontents: true,\n\t\tnext: true,\n\t\tprev: true\n\t};\n\njQuery.extend({\n\tdir: function( elem, dir, until ) {\n\t\tvar matched = [],\n\t\t\ttruncate = until !== undefined;\n\n\t\twhile ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) {\n\t\t\tif ( elem.nodeType === 1 ) {\n\t\t\t\tif ( truncate && jQuery( elem ).is( until ) ) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tmatched.push( elem );\n\t\t\t}\n\t\t}\n\t\treturn matched;\n\t},\n\n\tsibling: function( n, elem ) {\n\t\tvar matched = [];\n\n\t\tfor ( ; n; n = n.nextSibling ) {\n\t\t\tif ( n.nodeType === 1 && n !== elem ) {\n\t\t\t\tmatched.push( n );\n\t\t\t}\n\t\t}\n\n\t\treturn matched;\n\t}\n});\n\njQuery.fn.extend({\n\thas: function( target ) {\n\t\tvar targets = jQuery( target, this ),\n\t\t\tl = targets.length;\n\n\t\treturn this.filter(function() {\n\t\t\tvar i = 0;\n\t\t\tfor ( ; i < l; i++ ) {\n\t\t\t\tif ( jQuery.contains( this, targets[i] ) ) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t},\n\n\tclosest: function( selectors, context ) {\n\t\tvar cur,\n\t\t\ti = 0,\n\t\t\tl = this.length,\n\t\t\tmatched = [],\n\t\t\tpos = rneedsContext.test( selectors ) || typeof selectors !== \"string\" ?\n\t\t\t\tjQuery( selectors, context || this.context ) :\n\t\t\t\t0;\n\n\t\tfor ( ; i < l; i++ ) {\n\t\t\tfor ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) {\n\t\t\t\t// Always skip document fragments\n\t\t\t\tif ( cur.nodeType < 11 && (pos ?\n\t\t\t\t\tpos.index(cur) > -1 :\n\n\t\t\t\t\t// Don't pass non-elements to Sizzle\n\t\t\t\t\tcur.nodeType === 1 &&\n\t\t\t\t\t\tjQuery.find.matchesSelector(cur, selectors)) ) {\n\n\t\t\t\t\tmatched.push( cur );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched );\n\t},\n\n\t// Determine the position of an element within the set\n\tindex: function( elem ) {\n\n\t\t// No argument, return index in parent\n\t\tif ( !elem ) {\n\t\t\treturn ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;\n\t\t}\n\n\t\t// Index in selector\n\t\tif ( typeof elem === \"string\" ) {\n\t\t\treturn indexOf.call( jQuery( elem ), this[ 0 ] );\n\t\t}\n\n\t\t// Locate the position of the desired element\n\t\treturn indexOf.call( this,\n\n\t\t\t// If it receives a jQuery object, the first element is used\n\t\t\telem.jquery ? elem[ 0 ] : elem\n\t\t);\n\t},\n\n\tadd: function( selector, context ) {\n\t\treturn this.pushStack(\n\t\t\tjQuery.unique(\n\t\t\t\tjQuery.merge( this.get(), jQuery( selector, context ) )\n\t\t\t)\n\t\t);\n\t},\n\n\taddBack: function( selector ) {\n\t\treturn this.add( selector == null ?\n\t\t\tthis.prevObject : this.prevObject.filter(selector)\n\t\t);\n\t}\n});\n\nfunction sibling( cur, dir ) {\n\twhile ( (cur = cur[dir]) && cur.nodeType !== 1 ) {}\n\treturn cur;\n}\n\njQuery.each({\n\tparent: function( elem ) {\n\t\tvar parent = elem.parentNode;\n\t\treturn parent && parent.nodeType !== 11 ? parent : null;\n\t},\n\tparents: function( elem ) {\n\t\treturn jQuery.dir( elem, \"parentNode\" );\n\t},\n\tparentsUntil: function( elem, i, until ) {\n\t\treturn jQuery.dir( elem, \"parentNode\", until );\n\t},\n\tnext: function( elem ) {\n\t\treturn sibling( elem, \"nextSibling\" );\n\t},\n\tprev: function( elem ) {\n\t\treturn sibling( elem, \"previousSibling\" );\n\t},\n\tnextAll: function( elem ) {\n\t\treturn jQuery.dir( elem, \"nextSibling\" );\n\t},\n\tprevAll: function( elem ) {\n\t\treturn jQuery.dir( elem, \"previousSibling\" );\n\t},\n\tnextUntil: function( elem, i, until ) {\n\t\treturn jQuery.dir( elem, \"nextSibling\", until );\n\t},\n\tprevUntil: function( elem, i, until ) {\n\t\treturn jQuery.dir( elem, \"previousSibling\", until );\n\t},\n\tsiblings: function( elem ) {\n\t\treturn jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );\n\t},\n\tchildren: function( elem ) {\n\t\treturn jQuery.sibling( elem.firstChild );\n\t},\n\tcontents: function( elem ) {\n\t\treturn elem.contentDocument || jQuery.merge( [], elem.childNodes );\n\t}\n}, function( name, fn ) {\n\tjQuery.fn[ name ] = function( until, selector ) {\n\t\tvar matched = jQuery.map( this, fn, until );\n\n\t\tif ( name.slice( -5 ) !== \"Until\" ) {\n\t\t\tselector = until;\n\t\t}\n\n\t\tif ( selector && typeof selector === \"string\" ) {\n\t\t\tmatched = jQuery.filter( selector, matched );\n\t\t}\n\n\t\tif ( this.length > 1 ) {\n\t\t\t// Remove duplicates\n\t\t\tif ( !guaranteedUnique[ name ] ) {\n\t\t\t\tjQuery.unique( matched );\n\t\t\t}\n\n\t\t\t// Reverse order for parents* and prev-derivatives\n\t\t\tif ( rparentsprev.test( name ) ) {\n\t\t\t\tmatched.reverse();\n\t\t\t}\n\t\t}\n\n\t\treturn this.pushStack( matched );\n\t};\n});\nvar rnotwhite = (/\\S+/g);\n\n\n\n// String to Object options format cache\nvar optionsCache = {};\n\n// Convert String-formatted options into Object-formatted ones and store in cache\nfunction createOptions( options ) {\n\tvar object = optionsCache[ options ] = {};\n\tjQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {\n\t\tobject[ flag ] = true;\n\t});\n\treturn object;\n}\n\n/*\n * Create a callback list using the following parameters:\n *\n *\toptions: an optional list of space-separated options that will change how\n *\t\t\tthe callback list behaves or a more traditional option object\n *\n * By default a callback list will act like an event callback list and can be\n * \"fired\" multiple times.\n *\n * Possible options:\n *\n *\tonce:\t\t\twill ensure the callback list can only be fired once (like a Deferred)\n *\n *\tmemory:\t\t\twill keep track of previous values and will call any callback added\n *\t\t\t\t\tafter the list has been fired right away with the latest \"memorized\"\n *\t\t\t\t\tvalues (like a Deferred)\n *\n *\tunique:\t\t\twill ensure a callback can only be added once (no duplicate in the list)\n *\n *\tstopOnFalse:\tinterrupt callings when a callback returns false\n *\n */\njQuery.Callbacks = function( options ) {\n\n\t// Convert options from String-formatted to Object-formatted if needed\n\t// (we check in cache first)\n\toptions = typeof options === \"string\" ?\n\t\t( optionsCache[ options ] || createOptions( options ) ) :\n\t\tjQuery.extend( {}, options );\n\n\tvar // Last fire value (for non-forgettable lists)\n\t\tmemory,\n\t\t// Flag to know if list was already fired\n\t\tfired,\n\t\t// Flag to know if list is currently firing\n\t\tfiring,\n\t\t// First callback to fire (used internally by add and fireWith)\n\t\tfiringStart,\n\t\t// End of the loop when firing\n\t\tfiringLength,\n\t\t// Index of currently firing callback (modified by remove if needed)\n\t\tfiringIndex,\n\t\t// Actual callback list\n\t\tlist = [],\n\t\t// Stack of fire calls for repeatable lists\n\t\tstack = !options.once && [],\n\t\t// Fire callbacks\n\t\tfire = function( data ) {\n\t\t\tmemory = options.memory && data;\n\t\t\tfired = true;\n\t\t\tfiringIndex = firingStart || 0;\n\t\t\tfiringStart = 0;\n\t\t\tfiringLength = list.length;\n\t\t\tfiring = true;\n\t\t\tfor ( ; list && firingIndex < firingLength; firingIndex++ ) {\n\t\t\t\tif ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {\n\t\t\t\t\tmemory = false; // To prevent further calls using add\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfiring = false;\n\t\t\tif ( list ) {\n\t\t\t\tif ( stack ) {\n\t\t\t\t\tif ( stack.length ) {\n\t\t\t\t\t\tfire( stack.shift() );\n\t\t\t\t\t}\n\t\t\t\t} else if ( memory ) {\n\t\t\t\t\tlist = [];\n\t\t\t\t} else {\n\t\t\t\t\tself.disable();\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t// Actual Callbacks object\n\t\tself = {\n\t\t\t// Add a callback or a collection of callbacks to the list\n\t\t\tadd: function() {\n\t\t\t\tif ( list ) {\n\t\t\t\t\t// First, we save the current length\n\t\t\t\t\tvar start = list.length;\n\t\t\t\t\t(function add( args ) {\n\t\t\t\t\t\tjQuery.each( args, function( _, arg ) {\n\t\t\t\t\t\t\tvar type = jQuery.type( arg );\n\t\t\t\t\t\t\tif ( type === \"function\" ) {\n\t\t\t\t\t\t\t\tif ( !options.unique || !self.has( arg ) ) {\n\t\t\t\t\t\t\t\t\tlist.push( arg );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if ( arg && arg.length && type !== \"string\" ) {\n\t\t\t\t\t\t\t\t// Inspect recursively\n\t\t\t\t\t\t\t\tadd( arg );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t})( arguments );\n\t\t\t\t\t// Do we need to add the callbacks to the\n\t\t\t\t\t// current firing batch?\n\t\t\t\t\tif ( firing ) {\n\t\t\t\t\t\tfiringLength = list.length;\n\t\t\t\t\t// With memory, if we're not firing then\n\t\t\t\t\t// we should call right away\n\t\t\t\t\t} else if ( memory ) {\n\t\t\t\t\t\tfiringStart = start;\n\t\t\t\t\t\tfire( memory );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Remove a callback from the list\n\t\t\tremove: function() {\n\t\t\t\tif ( list ) {\n\t\t\t\t\tjQuery.each( arguments, function( _, arg ) {\n\t\t\t\t\t\tvar index;\n\t\t\t\t\t\twhile ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {\n\t\t\t\t\t\t\tlist.splice( index, 1 );\n\t\t\t\t\t\t\t// Handle firing indexes\n\t\t\t\t\t\t\tif ( firing ) {\n\t\t\t\t\t\t\t\tif ( index <= firingLength ) {\n\t\t\t\t\t\t\t\t\tfiringLength--;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif ( index <= firingIndex ) {\n\t\t\t\t\t\t\t\t\tfiringIndex--;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Check if a given callback is in the list.\n\t\t\t// If no argument is given, return whether or not list has callbacks attached.\n\t\t\thas: function( fn ) {\n\t\t\t\treturn fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );\n\t\t\t},\n\t\t\t// Remove all callbacks from the list\n\t\t\tempty: function() {\n\t\t\t\tlist = [];\n\t\t\t\tfiringLength = 0;\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Have the list do nothing anymore\n\t\t\tdisable: function() {\n\t\t\t\tlist = stack = memory = undefined;\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Is it disabled?\n\t\t\tdisabled: function() {\n\t\t\t\treturn !list;\n\t\t\t},\n\t\t\t// Lock the list in its current state\n\t\t\tlock: function() {\n\t\t\t\tstack = undefined;\n\t\t\t\tif ( !memory ) {\n\t\t\t\t\tself.disable();\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Is it locked?\n\t\t\tlocked: function() {\n\t\t\t\treturn !stack;\n\t\t\t},\n\t\t\t// Call all callbacks with the given context and arguments\n\t\t\tfireWith: function( context, args ) {\n\t\t\t\tif ( list && ( !fired || stack ) ) {\n\t\t\t\t\targs = args || [];\n\t\t\t\t\targs = [ context, args.slice ? args.slice() : args ];\n\t\t\t\t\tif ( firing ) {\n\t\t\t\t\t\tstack.push( args );\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfire( args );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Call all the callbacks with the given arguments\n\t\t\tfire: function() {\n\t\t\t\tself.fireWith( this, arguments );\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// To know if the callbacks have already been called at least once\n\t\t\tfired: function() {\n\t\t\t\treturn !!fired;\n\t\t\t}\n\t\t};\n\n\treturn self;\n};\n\n\njQuery.extend({\n\n\tDeferred: function( func ) {\n\t\tvar tuples = [\n\t\t\t\t// action, add listener, listener list, final state\n\t\t\t\t[ \"resolve\", \"done\", jQuery.Callbacks(\"once memory\"), \"resolved\" ],\n\t\t\t\t[ \"reject\", \"fail\", jQuery.Callbacks(\"once memory\"), \"rejected\" ],\n\t\t\t\t[ \"notify\", \"progress\", jQuery.Callbacks(\"memory\") ]\n\t\t\t],\n\t\t\tstate = \"pending\",\n\t\t\tpromise = {\n\t\t\t\tstate: function() {\n\t\t\t\t\treturn state;\n\t\t\t\t},\n\t\t\t\talways: function() {\n\t\t\t\t\tdeferred.done( arguments ).fail( arguments );\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\t\t\t\tthen: function( /* fnDone, fnFail, fnProgress */ ) {\n\t\t\t\t\tvar fns = arguments;\n\t\t\t\t\treturn jQuery.Deferred(function( newDefer ) {\n\t\t\t\t\t\tjQuery.each( tuples, function( i, tuple ) {\n\t\t\t\t\t\t\tvar fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];\n\t\t\t\t\t\t\t// deferred[ done | fail | progress ] for forwarding actions to newDefer\n\t\t\t\t\t\t\tdeferred[ tuple[1] ](function() {\n\t\t\t\t\t\t\t\tvar returned = fn && fn.apply( this, arguments );\n\t\t\t\t\t\t\t\tif ( returned && jQuery.isFunction( returned.promise ) ) {\n\t\t\t\t\t\t\t\t\treturned.promise()\n\t\t\t\t\t\t\t\t\t\t.done( newDefer.resolve )\n\t\t\t\t\t\t\t\t\t\t.fail( newDefer.reject )\n\t\t\t\t\t\t\t\t\t\t.progress( newDefer.notify );\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tnewDefer[ tuple[ 0 ] + \"With\" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\t\t\t\t\t\tfns = null;\n\t\t\t\t\t}).promise();\n\t\t\t\t},\n\t\t\t\t// Get a promise for this deferred\n\t\t\t\t// If obj is provided, the promise aspect is added to the object\n\t\t\t\tpromise: function( obj ) {\n\t\t\t\t\treturn obj != null ? jQuery.extend( obj, promise ) : promise;\n\t\t\t\t}\n\t\t\t},\n\t\t\tdeferred = {};\n\n\t\t// Keep pipe for back-compat\n\t\tpromise.pipe = promise.then;\n\n\t\t// Add list-specific methods\n\t\tjQuery.each( tuples, function( i, tuple ) {\n\t\t\tvar list = tuple[ 2 ],\n\t\t\t\tstateString = tuple[ 3 ];\n\n\t\t\t// promise[ done | fail | progress ] = list.add\n\t\t\tpromise[ tuple[1] ] = list.add;\n\n\t\t\t// Handle state\n\t\t\tif ( stateString ) {\n\t\t\t\tlist.add(function() {\n\t\t\t\t\t// state = [ resolved | rejected ]\n\t\t\t\t\tstate = stateString;\n\n\t\t\t\t// [ reject_list | resolve_list ].disable; progress_list.lock\n\t\t\t\t}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );\n\t\t\t}\n\n\t\t\t// deferred[ resolve | reject | notify ]\n\t\t\tdeferred[ tuple[0] ] = function() {\n\t\t\t\tdeferred[ tuple[0] + \"With\" ]( this === deferred ? promise : this, arguments );\n\t\t\t\treturn this;\n\t\t\t};\n\t\t\tdeferred[ tuple[0] + \"With\" ] = list.fireWith;\n\t\t});\n\n\t\t// Make the deferred a promise\n\t\tpromise.promise( deferred );\n\n\t\t// Call given func if any\n\t\tif ( func ) {\n\t\t\tfunc.call( deferred, deferred );\n\t\t}\n\n\t\t// All done!\n\t\treturn deferred;\n\t},\n\n\t// Deferred helper\n\twhen: function( subordinate /* , ..., subordinateN */ ) {\n\t\tvar i = 0,\n\t\t\tresolveValues = slice.call( arguments ),\n\t\t\tlength = resolveValues.length,\n\n\t\t\t// the count of uncompleted subordinates\n\t\t\tremaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,\n\n\t\t\t// the master Deferred. If resolveValues consist of only a single Deferred, just use that.\n\t\t\tdeferred = remaining === 1 ? subordinate : jQuery.Deferred(),\n\n\t\t\t// Update function for both resolve and progress values\n\t\t\tupdateFunc = function( i, contexts, values ) {\n\t\t\t\treturn function( value ) {\n\t\t\t\t\tcontexts[ i ] = this;\n\t\t\t\t\tvalues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;\n\t\t\t\t\tif ( values === progressValues ) {\n\t\t\t\t\t\tdeferred.notifyWith( contexts, values );\n\t\t\t\t\t} else if ( !( --remaining ) ) {\n\t\t\t\t\t\tdeferred.resolveWith( contexts, values );\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t},\n\n\t\t\tprogressValues, progressContexts, resolveContexts;\n\n\t\t// Add listeners to Deferred subordinates; treat others as resolved\n\t\tif ( length > 1 ) {\n\t\t\tprogressValues = new Array( length );\n\t\t\tprogressContexts = new Array( length );\n\t\t\tresolveContexts = new Array( length );\n\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\tif ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {\n\t\t\t\t\tresolveValues[ i ].promise()\n\t\t\t\t\t\t.done( updateFunc( i, resolveContexts, resolveValues ) )\n\t\t\t\t\t\t.fail( deferred.reject )\n\t\t\t\t\t\t.progress( updateFunc( i, progressContexts, progressValues ) );\n\t\t\t\t} else {\n\t\t\t\t\t--remaining;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// If we're not waiting on anything, resolve the master\n\t\tif ( !remaining ) {\n\t\t\tdeferred.resolveWith( resolveContexts, resolveValues );\n\t\t}\n\n\t\treturn deferred.promise();\n\t}\n});\n\n\n// The deferred used on DOM ready\nvar readyList;\n\njQuery.fn.ready = function( fn ) {\n\t// Add the callback\n\tjQuery.ready.promise().done( fn );\n\n\treturn this;\n};\n\njQuery.extend({\n\t// Is the DOM ready to be used? Set to true once it occurs.\n\tisReady: false,\n\n\t// A counter to track how many items to wait for before\n\t// the ready event fires. See #6781\n\treadyWait: 1,\n\n\t// Hold (or release) the ready event\n\tholdReady: function( hold ) {\n\t\tif ( hold ) {\n\t\t\tjQuery.readyWait++;\n\t\t} else {\n\t\t\tjQuery.ready( true );\n\t\t}\n\t},\n\n\t// Handle when the DOM is ready\n\tready: function( wait ) {\n\n\t\t// Abort if there are pending holds or we're already ready\n\t\tif ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Remember that the DOM is ready\n\t\tjQuery.isReady = true;\n\n\t\t// If a normal DOM Ready event fired, decrement, and wait if need be\n\t\tif ( wait !== true && --jQuery.readyWait > 0 ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// If there are functions bound, to execute\n\t\treadyList.resolveWith( document, [ jQuery ] );\n\n\t\t// Trigger any bound ready events\n\t\tif ( jQuery.fn.triggerHandler ) {\n\t\t\tjQuery( document ).triggerHandler( \"ready\" );\n\t\t\tjQuery( document ).off( \"ready\" );\n\t\t}\n\t}\n});\n\n/**\n * The ready event handler and self cleanup method\n */\nfunction completed() {\n\tdocument.removeEventListener( \"DOMContentLoaded\", completed, false );\n\twindow.removeEventListener( \"load\", completed, false );\n\tjQuery.ready();\n}\n\njQuery.ready.promise = function( obj ) {\n\tif ( !readyList ) {\n\n\t\treadyList = jQuery.Deferred();\n\n\t\t// Catch cases where $(document).ready() is called after the browser event has already occurred.\n\t\t// We once tried to use readyState \"interactive\" here, but it caused issues like the one\n\t\t// discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15\n\t\tif ( document.readyState === \"complete\" ) {\n\t\t\t// Handle it asynchronously to allow scripts the opportunity to delay ready\n\t\t\tsetTimeout( jQuery.ready );\n\n\t\t} else {\n\n\t\t\t// Use the handy event callback\n\t\t\tdocument.addEventListener( \"DOMContentLoaded\", completed, false );\n\n\t\t\t// A fallback to window.onload, that will always work\n\t\t\twindow.addEventListener( \"load\", completed, false );\n\t\t}\n\t}\n\treturn readyList.promise( obj );\n};\n\n// Kick off the DOM ready check even if the user does not\njQuery.ready.promise();\n\n\n\n\n// Multifunctional method to get and set values of a collection\n// The value/s can optionally be executed if it's a function\nvar access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) {\n\tvar i = 0,\n\t\tlen = elems.length,\n\t\tbulk = key == null;\n\n\t// Sets many values\n\tif ( jQuery.type( key ) === \"object\" ) {\n\t\tchainable = true;\n\t\tfor ( i in key ) {\n\t\t\tjQuery.access( elems, fn, i, key[i], true, emptyGet, raw );\n\t\t}\n\n\t// Sets one value\n\t} else if ( value !== undefined ) {\n\t\tchainable = true;\n\n\t\tif ( !jQuery.isFunction( value ) ) {\n\t\t\traw = true;\n\t\t}\n\n\t\tif ( bulk ) {\n\t\t\t// Bulk operations run against the entire set\n\t\t\tif ( raw ) {\n\t\t\t\tfn.call( elems, value );\n\t\t\t\tfn = null;\n\n\t\t\t// ...except when executing function values\n\t\t\t} else {\n\t\t\t\tbulk = fn;\n\t\t\t\tfn = function( elem, key, value ) {\n\t\t\t\t\treturn bulk.call( jQuery( elem ), value );\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tif ( fn ) {\n\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\tfn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );\n\t\t\t}\n\t\t}\n\t}\n\n\treturn chainable ?\n\t\telems :\n\n\t\t// Gets\n\t\tbulk ?\n\t\t\tfn.call( elems ) :\n\t\t\tlen ? fn( elems[0], key ) : emptyGet;\n};\n\n\n/**\n * Determines whether an object can have data\n */\njQuery.acceptData = function( owner ) {\n\t// Accepts only:\n\t//  - Node\n\t//    - Node.ELEMENT_NODE\n\t//    - Node.DOCUMENT_NODE\n\t//  - Object\n\t//    - Any\n\t/* jshint -W018 */\n\treturn owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );\n};\n\n\nfunction Data() {\n\t// Support: Android<4,\n\t// Old WebKit does not have Object.preventExtensions/freeze method,\n\t// return new empty object instead with no [[set]] accessor\n\tObject.defineProperty( this.cache = {}, 0, {\n\t\tget: function() {\n\t\t\treturn {};\n\t\t}\n\t});\n\n\tthis.expando = jQuery.expando + Data.uid++;\n}\n\nData.uid = 1;\nData.accepts = jQuery.acceptData;\n\nData.prototype = {\n\tkey: function( owner ) {\n\t\t// We can accept data for non-element nodes in modern browsers,\n\t\t// but we should not, see #8335.\n\t\t// Always return the key for a frozen object.\n\t\tif ( !Data.accepts( owner ) ) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tvar descriptor = {},\n\t\t\t// Check if the owner object already has a cache key\n\t\t\tunlock = owner[ this.expando ];\n\n\t\t// If not, create one\n\t\tif ( !unlock ) {\n\t\t\tunlock = Data.uid++;\n\n\t\t\t// Secure it in a non-enumerable, non-writable property\n\t\t\ttry {\n\t\t\t\tdescriptor[ this.expando ] = { value: unlock };\n\t\t\t\tObject.defineProperties( owner, descriptor );\n\n\t\t\t// Support: Android<4\n\t\t\t// Fallback to a less secure definition\n\t\t\t} catch ( e ) {\n\t\t\t\tdescriptor[ this.expando ] = unlock;\n\t\t\t\tjQuery.extend( owner, descriptor );\n\t\t\t}\n\t\t}\n\n\t\t// Ensure the cache object\n\t\tif ( !this.cache[ unlock ] ) {\n\t\t\tthis.cache[ unlock ] = {};\n\t\t}\n\n\t\treturn unlock;\n\t},\n\tset: function( owner, data, value ) {\n\t\tvar prop,\n\t\t\t// There may be an unlock assigned to this node,\n\t\t\t// if there is no entry for this \"owner\", create one inline\n\t\t\t// and set the unlock as though an owner entry had always existed\n\t\t\tunlock = this.key( owner ),\n\t\t\tcache = this.cache[ unlock ];\n\n\t\t// Handle: [ owner, key, value ] args\n\t\tif ( typeof data === \"string\" ) {\n\t\t\tcache[ data ] = value;\n\n\t\t// Handle: [ owner, { properties } ] args\n\t\t} else {\n\t\t\t// Fresh assignments by object are shallow copied\n\t\t\tif ( jQuery.isEmptyObject( cache ) ) {\n\t\t\t\tjQuery.extend( this.cache[ unlock ], data );\n\t\t\t// Otherwise, copy the properties one-by-one to the cache object\n\t\t\t} else {\n\t\t\t\tfor ( prop in data ) {\n\t\t\t\t\tcache[ prop ] = data[ prop ];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn cache;\n\t},\n\tget: function( owner, key ) {\n\t\t// Either a valid cache is found, or will be created.\n\t\t// New caches will be created and the unlock returned,\n\t\t// allowing direct access to the newly created\n\t\t// empty data object. A valid owner object must be provided.\n\t\tvar cache = this.cache[ this.key( owner ) ];\n\n\t\treturn key === undefined ?\n\t\t\tcache : cache[ key ];\n\t},\n\taccess: function( owner, key, value ) {\n\t\tvar stored;\n\t\t// In cases where either:\n\t\t//\n\t\t//   1. No key was specified\n\t\t//   2. A string key was specified, but no value provided\n\t\t//\n\t\t// Take the \"read\" path and allow the get method to determine\n\t\t// which value to return, respectively either:\n\t\t//\n\t\t//   1. The entire cache object\n\t\t//   2. The data stored at the key\n\t\t//\n\t\tif ( key === undefined ||\n\t\t\t\t((key && typeof key === \"string\") && value === undefined) ) {\n\n\t\t\tstored = this.get( owner, key );\n\n\t\t\treturn stored !== undefined ?\n\t\t\t\tstored : this.get( owner, jQuery.camelCase(key) );\n\t\t}\n\n\t\t// [*]When the key is not a string, or both a key and value\n\t\t// are specified, set or extend (existing objects) with either:\n\t\t//\n\t\t//   1. An object of properties\n\t\t//   2. A key and value\n\t\t//\n\t\tthis.set( owner, key, value );\n\n\t\t// Since the \"set\" path can have two possible entry points\n\t\t// return the expected data based on which path was taken[*]\n\t\treturn value !== undefined ? value : key;\n\t},\n\tremove: function( owner, key ) {\n\t\tvar i, name, camel,\n\t\t\tunlock = this.key( owner ),\n\t\t\tcache = this.cache[ unlock ];\n\n\t\tif ( key === undefined ) {\n\t\t\tthis.cache[ unlock ] = {};\n\n\t\t} else {\n\t\t\t// Support array or space separated string of keys\n\t\t\tif ( jQuery.isArray( key ) ) {\n\t\t\t\t// If \"name\" is an array of keys...\n\t\t\t\t// When data is initially created, via (\"key\", \"val\") signature,\n\t\t\t\t// keys will be converted to camelCase.\n\t\t\t\t// Since there is no way to tell _how_ a key was added, remove\n\t\t\t\t// both plain key and camelCase key. #12786\n\t\t\t\t// This will only penalize the array argument path.\n\t\t\t\tname = key.concat( key.map( jQuery.camelCase ) );\n\t\t\t} else {\n\t\t\t\tcamel = jQuery.camelCase( key );\n\t\t\t\t// Try the string as a key before any manipulation\n\t\t\t\tif ( key in cache ) {\n\t\t\t\t\tname = [ key, camel ];\n\t\t\t\t} else {\n\t\t\t\t\t// If a key with the spaces exists, use it.\n\t\t\t\t\t// Otherwise, create an array by matching non-whitespace\n\t\t\t\t\tname = camel;\n\t\t\t\t\tname = name in cache ?\n\t\t\t\t\t\t[ name ] : ( name.match( rnotwhite ) || [] );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ti = name.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\tdelete cache[ name[ i ] ];\n\t\t\t}\n\t\t}\n\t},\n\thasData: function( owner ) {\n\t\treturn !jQuery.isEmptyObject(\n\t\t\tthis.cache[ owner[ this.expando ] ] || {}\n\t\t);\n\t},\n\tdiscard: function( owner ) {\n\t\tif ( owner[ this.expando ] ) {\n\t\t\tdelete this.cache[ owner[ this.expando ] ];\n\t\t}\n\t}\n};\nvar data_priv = new Data();\n\nvar data_user = new Data();\n\n\n\n//\tImplementation Summary\n//\n//\t1. Enforce API surface and semantic compatibility with 1.9.x branch\n//\t2. Improve the module's maintainability by reducing the storage\n//\t\tpaths to a single mechanism.\n//\t3. Use the same single mechanism to support \"private\" and \"user\" data.\n//\t4. _Never_ expose \"private\" data to user code (TODO: Drop _data, _removeData)\n//\t5. Avoid exposing implementation details on user objects (eg. expando properties)\n//\t6. Provide a clear path for implementation upgrade to WeakMap in 2014\n\nvar rbrace = /^(?:\\{[\\w\\W]*\\}|\\[[\\w\\W]*\\])$/,\n\trmultiDash = /([A-Z])/g;\n\nfunction dataAttr( elem, key, data ) {\n\tvar name;\n\n\t// If nothing was found internally, try to fetch any\n\t// data from the HTML5 data-* attribute\n\tif ( data === undefined && elem.nodeType === 1 ) {\n\t\tname = \"data-\" + key.replace( rmultiDash, \"-$1\" ).toLowerCase();\n\t\tdata = elem.getAttribute( name );\n\n\t\tif ( typeof data === \"string\" ) {\n\t\t\ttry {\n\t\t\t\tdata = data === \"true\" ? true :\n\t\t\t\t\tdata === \"false\" ? false :\n\t\t\t\t\tdata === \"null\" ? null :\n\t\t\t\t\t// Only convert to a number if it doesn't change the string\n\t\t\t\t\t+data + \"\" === data ? +data :\n\t\t\t\t\trbrace.test( data ) ? jQuery.parseJSON( data ) :\n\t\t\t\t\tdata;\n\t\t\t} catch( e ) {}\n\n\t\t\t// Make sure we set the data so it isn't changed later\n\t\t\tdata_user.set( elem, key, data );\n\t\t} else {\n\t\t\tdata = undefined;\n\t\t}\n\t}\n\treturn data;\n}\n\njQuery.extend({\n\thasData: function( elem ) {\n\t\treturn data_user.hasData( elem ) || data_priv.hasData( elem );\n\t},\n\n\tdata: function( elem, name, data ) {\n\t\treturn data_user.access( elem, name, data );\n\t},\n\n\tremoveData: function( elem, name ) {\n\t\tdata_user.remove( elem, name );\n\t},\n\n\t// TODO: Now that all calls to _data and _removeData have been replaced\n\t// with direct calls to data_priv methods, these can be deprecated.\n\t_data: function( elem, name, data ) {\n\t\treturn data_priv.access( elem, name, data );\n\t},\n\n\t_removeData: function( elem, name ) {\n\t\tdata_priv.remove( elem, name );\n\t}\n});\n\njQuery.fn.extend({\n\tdata: function( key, value ) {\n\t\tvar i, name, data,\n\t\t\telem = this[ 0 ],\n\t\t\tattrs = elem && elem.attributes;\n\n\t\t// Gets all values\n\t\tif ( key === undefined ) {\n\t\t\tif ( this.length ) {\n\t\t\t\tdata = data_user.get( elem );\n\n\t\t\t\tif ( elem.nodeType === 1 && !data_priv.get( elem, \"hasDataAttrs\" ) ) {\n\t\t\t\t\ti = attrs.length;\n\t\t\t\t\twhile ( i-- ) {\n\n\t\t\t\t\t\t// Support: IE11+\n\t\t\t\t\t\t// The attrs elements can be null (#14894)\n\t\t\t\t\t\tif ( attrs[ i ] ) {\n\t\t\t\t\t\t\tname = attrs[ i ].name;\n\t\t\t\t\t\t\tif ( name.indexOf( \"data-\" ) === 0 ) {\n\t\t\t\t\t\t\t\tname = jQuery.camelCase( name.slice(5) );\n\t\t\t\t\t\t\t\tdataAttr( elem, name, data[ name ] );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tdata_priv.set( elem, \"hasDataAttrs\", true );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn data;\n\t\t}\n\n\t\t// Sets multiple values\n\t\tif ( typeof key === \"object\" ) {\n\t\t\treturn this.each(function() {\n\t\t\t\tdata_user.set( this, key );\n\t\t\t});\n\t\t}\n\n\t\treturn access( this, function( value ) {\n\t\t\tvar data,\n\t\t\t\tcamelKey = jQuery.camelCase( key );\n\n\t\t\t// The calling jQuery object (element matches) is not empty\n\t\t\t// (and therefore has an element appears at this[ 0 ]) and the\n\t\t\t// `value` parameter was not undefined. An empty jQuery object\n\t\t\t// will result in `undefined` for elem = this[ 0 ] which will\n\t\t\t// throw an exception if an attempt to read a data cache is made.\n\t\t\tif ( elem && value === undefined ) {\n\t\t\t\t// Attempt to get data from the cache\n\t\t\t\t// with the key as-is\n\t\t\t\tdata = data_user.get( elem, key );\n\t\t\t\tif ( data !== undefined ) {\n\t\t\t\t\treturn data;\n\t\t\t\t}\n\n\t\t\t\t// Attempt to get data from the cache\n\t\t\t\t// with the key camelized\n\t\t\t\tdata = data_user.get( elem, camelKey );\n\t\t\t\tif ( data !== undefined ) {\n\t\t\t\t\treturn data;\n\t\t\t\t}\n\n\t\t\t\t// Attempt to \"discover\" the data in\n\t\t\t\t// HTML5 custom data-* attrs\n\t\t\t\tdata = dataAttr( elem, camelKey, undefined );\n\t\t\t\tif ( data !== undefined ) {\n\t\t\t\t\treturn data;\n\t\t\t\t}\n\n\t\t\t\t// We tried really hard, but the data doesn't exist.\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Set the data...\n\t\t\tthis.each(function() {\n\t\t\t\t// First, attempt to store a copy or reference of any\n\t\t\t\t// data that might've been store with a camelCased key.\n\t\t\t\tvar data = data_user.get( this, camelKey );\n\n\t\t\t\t// For HTML5 data-* attribute interop, we have to\n\t\t\t\t// store property names with dashes in a camelCase form.\n\t\t\t\t// This might not apply to all properties...*\n\t\t\t\tdata_user.set( this, camelKey, value );\n\n\t\t\t\t// *... In the case of properties that might _actually_\n\t\t\t\t// have dashes, we need to also store a copy of that\n\t\t\t\t// unchanged property.\n\t\t\t\tif ( key.indexOf(\"-\") !== -1 && data !== undefined ) {\n\t\t\t\t\tdata_user.set( this, key, value );\n\t\t\t\t}\n\t\t\t});\n\t\t}, null, value, arguments.length > 1, null, true );\n\t},\n\n\tremoveData: function( key ) {\n\t\treturn this.each(function() {\n\t\t\tdata_user.remove( this, key );\n\t\t});\n\t}\n});\n\n\njQuery.extend({\n\tqueue: function( elem, type, data ) {\n\t\tvar queue;\n\n\t\tif ( elem ) {\n\t\t\ttype = ( type || \"fx\" ) + \"queue\";\n\t\t\tqueue = data_priv.get( elem, type );\n\n\t\t\t// Speed up dequeue by getting out quickly if this is just a lookup\n\t\t\tif ( data ) {\n\t\t\t\tif ( !queue || jQuery.isArray( data ) ) {\n\t\t\t\t\tqueue = data_priv.access( elem, type, jQuery.makeArray(data) );\n\t\t\t\t} else {\n\t\t\t\t\tqueue.push( data );\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn queue || [];\n\t\t}\n\t},\n\n\tdequeue: function( elem, type ) {\n\t\ttype = type || \"fx\";\n\n\t\tvar queue = jQuery.queue( elem, type ),\n\t\t\tstartLength = queue.length,\n\t\t\tfn = queue.shift(),\n\t\t\thooks = jQuery._queueHooks( elem, type ),\n\t\t\tnext = function() {\n\t\t\t\tjQuery.dequeue( elem, type );\n\t\t\t};\n\n\t\t// If the fx queue is dequeued, always remove the progress sentinel\n\t\tif ( fn === \"inprogress\" ) {\n\t\t\tfn = queue.shift();\n\t\t\tstartLength--;\n\t\t}\n\n\t\tif ( fn ) {\n\n\t\t\t// Add a progress sentinel to prevent the fx queue from being\n\t\t\t// automatically dequeued\n\t\t\tif ( type === \"fx\" ) {\n\t\t\t\tqueue.unshift( \"inprogress\" );\n\t\t\t}\n\n\t\t\t// Clear up the last queue stop function\n\t\t\tdelete hooks.stop;\n\t\t\tfn.call( elem, next, hooks );\n\t\t}\n\n\t\tif ( !startLength && hooks ) {\n\t\t\thooks.empty.fire();\n\t\t}\n\t},\n\n\t// Not public - generate a queueHooks object, or return the current one\n\t_queueHooks: function( elem, type ) {\n\t\tvar key = type + \"queueHooks\";\n\t\treturn data_priv.get( elem, key ) || data_priv.access( elem, key, {\n\t\t\tempty: jQuery.Callbacks(\"once memory\").add(function() {\n\t\t\t\tdata_priv.remove( elem, [ type + \"queue\", key ] );\n\t\t\t})\n\t\t});\n\t}\n});\n\njQuery.fn.extend({\n\tqueue: function( type, data ) {\n\t\tvar setter = 2;\n\n\t\tif ( typeof type !== \"string\" ) {\n\t\t\tdata = type;\n\t\t\ttype = \"fx\";\n\t\t\tsetter--;\n\t\t}\n\n\t\tif ( arguments.length < setter ) {\n\t\t\treturn jQuery.queue( this[0], type );\n\t\t}\n\n\t\treturn data === undefined ?\n\t\t\tthis :\n\t\t\tthis.each(function() {\n\t\t\t\tvar queue = jQuery.queue( this, type, data );\n\n\t\t\t\t// Ensure a hooks for this queue\n\t\t\t\tjQuery._queueHooks( this, type );\n\n\t\t\t\tif ( type === \"fx\" && queue[0] !== \"inprogress\" ) {\n\t\t\t\t\tjQuery.dequeue( this, type );\n\t\t\t\t}\n\t\t\t});\n\t},\n\tdequeue: function( type ) {\n\t\treturn this.each(function() {\n\t\t\tjQuery.dequeue( this, type );\n\t\t});\n\t},\n\tclearQueue: function( type ) {\n\t\treturn this.queue( type || \"fx\", [] );\n\t},\n\t// Get a promise resolved when queues of a certain type\n\t// are emptied (fx is the type by default)\n\tpromise: function( type, obj ) {\n\t\tvar tmp,\n\t\t\tcount = 1,\n\t\t\tdefer = jQuery.Deferred(),\n\t\t\telements = this,\n\t\t\ti = this.length,\n\t\t\tresolve = function() {\n\t\t\t\tif ( !( --count ) ) {\n\t\t\t\t\tdefer.resolveWith( elements, [ elements ] );\n\t\t\t\t}\n\t\t\t};\n\n\t\tif ( typeof type !== \"string\" ) {\n\t\t\tobj = type;\n\t\t\ttype = undefined;\n\t\t}\n\t\ttype = type || \"fx\";\n\n\t\twhile ( i-- ) {\n\t\t\ttmp = data_priv.get( elements[ i ], type + \"queueHooks\" );\n\t\t\tif ( tmp && tmp.empty ) {\n\t\t\t\tcount++;\n\t\t\t\ttmp.empty.add( resolve );\n\t\t\t}\n\t\t}\n\t\tresolve();\n\t\treturn defer.promise( obj );\n\t}\n});\nvar pnum = (/[+-]?(?:\\d*\\.|)\\d+(?:[eE][+-]?\\d+|)/).source;\n\nvar cssExpand = [ \"Top\", \"Right\", \"Bottom\", \"Left\" ];\n\nvar isHidden = function( elem, el ) {\n\t\t// isHidden might be called from jQuery#filter function;\n\t\t// in that case, element will be second argument\n\t\telem = el || elem;\n\t\treturn jQuery.css( elem, \"display\" ) === \"none\" || !jQuery.contains( elem.ownerDocument, elem );\n\t};\n\nvar rcheckableType = (/^(?:checkbox|radio)$/i);\n\n\n\n(function() {\n\tvar fragment = document.createDocumentFragment(),\n\t\tdiv = fragment.appendChild( document.createElement( \"div\" ) ),\n\t\tinput = document.createElement( \"input\" );\n\n\t// Support: Safari<=5.1\n\t// Check state lost if the name is set (#11217)\n\t// Support: Windows Web Apps (WWA)\n\t// `name` and `type` must use .setAttribute for WWA (#14901)\n\tinput.setAttribute( \"type\", \"radio\" );\n\tinput.setAttribute( \"checked\", \"checked\" );\n\tinput.setAttribute( \"name\", \"t\" );\n\n\tdiv.appendChild( input );\n\n\t// Support: Safari<=5.1, Android<4.2\n\t// Older WebKit doesn't clone checked state correctly in fragments\n\tsupport.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;\n\n\t// Support: IE<=11+\n\t// Make sure textarea (and checkbox) defaultValue is properly cloned\n\tdiv.innerHTML = \"<textarea>x</textarea>\";\n\tsupport.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;\n})();\nvar strundefined = typeof undefined;\n\n\n\nsupport.focusinBubbles = \"onfocusin\" in window;\n\n\nvar\n\trkeyEvent = /^key/,\n\trmouseEvent = /^(?:mouse|pointer|contextmenu)|click/,\n\trfocusMorph = /^(?:focusinfocus|focusoutblur)$/,\n\trtypenamespace = /^([^.]*)(?:\\.(.+)|)$/;\n\nfunction returnTrue() {\n\treturn true;\n}\n\nfunction returnFalse() {\n\treturn false;\n}\n\nfunction safeActiveElement() {\n\ttry {\n\t\treturn document.activeElement;\n\t} catch ( err ) { }\n}\n\n/*\n * Helper functions for managing events -- not part of the public interface.\n * Props to Dean Edwards' addEvent library for many of the ideas.\n */\njQuery.event = {\n\n\tglobal: {},\n\n\tadd: function( elem, types, handler, data, selector ) {\n\n\t\tvar handleObjIn, eventHandle, tmp,\n\t\t\tevents, t, handleObj,\n\t\t\tspecial, handlers, type, namespaces, origType,\n\t\t\telemData = data_priv.get( elem );\n\n\t\t// Don't attach events to noData or text/comment nodes (but allow plain objects)\n\t\tif ( !elemData ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Caller can pass in an object of custom data in lieu of the handler\n\t\tif ( handler.handler ) {\n\t\t\thandleObjIn = handler;\n\t\t\thandler = handleObjIn.handler;\n\t\t\tselector = handleObjIn.selector;\n\t\t}\n\n\t\t// Make sure that the handler has a unique ID, used to find/remove it later\n\t\tif ( !handler.guid ) {\n\t\t\thandler.guid = jQuery.guid++;\n\t\t}\n\n\t\t// Init the element's event structure and main handler, if this is the first\n\t\tif ( !(events = elemData.events) ) {\n\t\t\tevents = elemData.events = {};\n\t\t}\n\t\tif ( !(eventHandle = elemData.handle) ) {\n\t\t\teventHandle = elemData.handle = function( e ) {\n\t\t\t\t// Discard the second event of a jQuery.event.trigger() and\n\t\t\t\t// when an event is called after a page has unloaded\n\t\t\t\treturn typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ?\n\t\t\t\t\tjQuery.event.dispatch.apply( elem, arguments ) : undefined;\n\t\t\t};\n\t\t}\n\n\t\t// Handle multiple events separated by a space\n\t\ttypes = ( types || \"\" ).match( rnotwhite ) || [ \"\" ];\n\t\tt = types.length;\n\t\twhile ( t-- ) {\n\t\t\ttmp = rtypenamespace.exec( types[t] ) || [];\n\t\t\ttype = origType = tmp[1];\n\t\t\tnamespaces = ( tmp[2] || \"\" ).split( \".\" ).sort();\n\n\t\t\t// There *must* be a type, no attaching namespace-only handlers\n\t\t\tif ( !type ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// If event changes its type, use the special event handlers for the changed type\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\n\t\t\t// If selector defined, determine special event api type, otherwise given type\n\t\t\ttype = ( selector ? special.delegateType : special.bindType ) || type;\n\n\t\t\t// Update special based on newly reset type\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\n\t\t\t// handleObj is passed to all event handlers\n\t\t\thandleObj = jQuery.extend({\n\t\t\t\ttype: type,\n\t\t\t\torigType: origType,\n\t\t\t\tdata: data,\n\t\t\t\thandler: handler,\n\t\t\t\tguid: handler.guid,\n\t\t\t\tselector: selector,\n\t\t\t\tneedsContext: selector && jQuery.expr.match.needsContext.test( selector ),\n\t\t\t\tnamespace: namespaces.join(\".\")\n\t\t\t}, handleObjIn );\n\n\t\t\t// Init the event handler queue if we're the first\n\t\t\tif ( !(handlers = events[ type ]) ) {\n\t\t\t\thandlers = events[ type ] = [];\n\t\t\t\thandlers.delegateCount = 0;\n\n\t\t\t\t// Only use addEventListener if the special events handler returns false\n\t\t\t\tif ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {\n\t\t\t\t\tif ( elem.addEventListener ) {\n\t\t\t\t\t\telem.addEventListener( type, eventHandle, false );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( special.add ) {\n\t\t\t\tspecial.add.call( elem, handleObj );\n\n\t\t\t\tif ( !handleObj.handler.guid ) {\n\t\t\t\t\thandleObj.handler.guid = handler.guid;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Add to the element's handler list, delegates in front\n\t\t\tif ( selector ) {\n\t\t\t\thandlers.splice( handlers.delegateCount++, 0, handleObj );\n\t\t\t} else {\n\t\t\t\thandlers.push( handleObj );\n\t\t\t}\n\n\t\t\t// Keep track of which events have ever been used, for event optimization\n\t\t\tjQuery.event.global[ type ] = true;\n\t\t}\n\n\t},\n\n\t// Detach an event or set of events from an element\n\tremove: function( elem, types, handler, selector, mappedTypes ) {\n\n\t\tvar j, origCount, tmp,\n\t\t\tevents, t, handleObj,\n\t\t\tspecial, handlers, type, namespaces, origType,\n\t\t\telemData = data_priv.hasData( elem ) && data_priv.get( elem );\n\n\t\tif ( !elemData || !(events = elemData.events) ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Once for each type.namespace in types; type may be omitted\n\t\ttypes = ( types || \"\" ).match( rnotwhite ) || [ \"\" ];\n\t\tt = types.length;\n\t\twhile ( t-- ) {\n\t\t\ttmp = rtypenamespace.exec( types[t] ) || [];\n\t\t\ttype = origType = tmp[1];\n\t\t\tnamespaces = ( tmp[2] || \"\" ).split( \".\" ).sort();\n\n\t\t\t// Unbind all events (on this namespace, if provided) for the element\n\t\t\tif ( !type ) {\n\t\t\t\tfor ( type in events ) {\n\t\t\t\t\tjQuery.event.remove( elem, type + types[ t ], handler, selector, true );\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\t\t\ttype = ( selector ? special.delegateType : special.bindType ) || type;\n\t\t\thandlers = events[ type ] || [];\n\t\t\ttmp = tmp[2] && new RegExp( \"(^|\\\\.)\" + namespaces.join(\"\\\\.(?:.*\\\\.|)\") + \"(\\\\.|$)\" );\n\n\t\t\t// Remove matching events\n\t\t\torigCount = j = handlers.length;\n\t\t\twhile ( j-- ) {\n\t\t\t\thandleObj = handlers[ j ];\n\n\t\t\t\tif ( ( mappedTypes || origType === handleObj.origType ) &&\n\t\t\t\t\t( !handler || handler.guid === handleObj.guid ) &&\n\t\t\t\t\t( !tmp || tmp.test( handleObj.namespace ) ) &&\n\t\t\t\t\t( !selector || selector === handleObj.selector || selector === \"**\" && handleObj.selector ) ) {\n\t\t\t\t\thandlers.splice( j, 1 );\n\n\t\t\t\t\tif ( handleObj.selector ) {\n\t\t\t\t\t\thandlers.delegateCount--;\n\t\t\t\t\t}\n\t\t\t\t\tif ( special.remove ) {\n\t\t\t\t\t\tspecial.remove.call( elem, handleObj );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Remove generic event handler if we removed something and no more handlers exist\n\t\t\t// (avoids potential for endless recursion during removal of special event handlers)\n\t\t\tif ( origCount && !handlers.length ) {\n\t\t\t\tif ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {\n\t\t\t\t\tjQuery.removeEvent( elem, type, elemData.handle );\n\t\t\t\t}\n\n\t\t\t\tdelete events[ type ];\n\t\t\t}\n\t\t}\n\n\t\t// Remove the expando if it's no longer used\n\t\tif ( jQuery.isEmptyObject( events ) ) {\n\t\t\tdelete elemData.handle;\n\t\t\tdata_priv.remove( elem, \"events\" );\n\t\t}\n\t},\n\n\ttrigger: function( event, data, elem, onlyHandlers ) {\n\n\t\tvar i, cur, tmp, bubbleType, ontype, handle, special,\n\t\t\teventPath = [ elem || document ],\n\t\t\ttype = hasOwn.call( event, \"type\" ) ? event.type : event,\n\t\t\tnamespaces = hasOwn.call( event, \"namespace\" ) ? event.namespace.split(\".\") : [];\n\n\t\tcur = tmp = elem = elem || document;\n\n\t\t// Don't do events on text and comment nodes\n\t\tif ( elem.nodeType === 3 || elem.nodeType === 8 ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// focus/blur morphs to focusin/out; ensure we're not firing them right now\n\t\tif ( rfocusMorph.test( type + jQuery.event.triggered ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( type.indexOf(\".\") >= 0 ) {\n\t\t\t// Namespaced trigger; create a regexp to match event type in handle()\n\t\t\tnamespaces = type.split(\".\");\n\t\t\ttype = namespaces.shift();\n\t\t\tnamespaces.sort();\n\t\t}\n\t\tontype = type.indexOf(\":\") < 0 && \"on\" + type;\n\n\t\t// Caller can pass in a jQuery.Event object, Object, or just an event type string\n\t\tevent = event[ jQuery.expando ] ?\n\t\t\tevent :\n\t\t\tnew jQuery.Event( type, typeof event === \"object\" && event );\n\n\t\t// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)\n\t\tevent.isTrigger = onlyHandlers ? 2 : 3;\n\t\tevent.namespace = namespaces.join(\".\");\n\t\tevent.namespace_re = event.namespace ?\n\t\t\tnew RegExp( \"(^|\\\\.)\" + namespaces.join(\"\\\\.(?:.*\\\\.|)\") + \"(\\\\.|$)\" ) :\n\t\t\tnull;\n\n\t\t// Clean up the event in case it is being reused\n\t\tevent.result = undefined;\n\t\tif ( !event.target ) {\n\t\t\tevent.target = elem;\n\t\t}\n\n\t\t// Clone any incoming data and prepend the event, creating the handler arg list\n\t\tdata = data == null ?\n\t\t\t[ event ] :\n\t\t\tjQuery.makeArray( data, [ event ] );\n\n\t\t// Allow special events to draw outside the lines\n\t\tspecial = jQuery.event.special[ type ] || {};\n\t\tif ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Determine event propagation path in advance, per W3C events spec (#9951)\n\t\t// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)\n\t\tif ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {\n\n\t\t\tbubbleType = special.delegateType || type;\n\t\t\tif ( !rfocusMorph.test( bubbleType + type ) ) {\n\t\t\t\tcur = cur.parentNode;\n\t\t\t}\n\t\t\tfor ( ; cur; cur = cur.parentNode ) {\n\t\t\t\teventPath.push( cur );\n\t\t\t\ttmp = cur;\n\t\t\t}\n\n\t\t\t// Only add window if we got to document (e.g., not plain obj or detached DOM)\n\t\t\tif ( tmp === (elem.ownerDocument || document) ) {\n\t\t\t\teventPath.push( tmp.defaultView || tmp.parentWindow || window );\n\t\t\t}\n\t\t}\n\n\t\t// Fire handlers on the event path\n\t\ti = 0;\n\t\twhile ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {\n\n\t\t\tevent.type = i > 1 ?\n\t\t\t\tbubbleType :\n\t\t\t\tspecial.bindType || type;\n\n\t\t\t// jQuery handler\n\t\t\thandle = ( data_priv.get( cur, \"events\" ) || {} )[ event.type ] && data_priv.get( cur, \"handle\" );\n\t\t\tif ( handle ) {\n\t\t\t\thandle.apply( cur, data );\n\t\t\t}\n\n\t\t\t// Native handler\n\t\t\thandle = ontype && cur[ ontype ];\n\t\t\tif ( handle && handle.apply && jQuery.acceptData( cur ) ) {\n\t\t\t\tevent.result = handle.apply( cur, data );\n\t\t\t\tif ( event.result === false ) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tevent.type = type;\n\n\t\t// If nobody prevented the default action, do it now\n\t\tif ( !onlyHandlers && !event.isDefaultPrevented() ) {\n\n\t\t\tif ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&\n\t\t\t\tjQuery.acceptData( elem ) ) {\n\n\t\t\t\t// Call a native DOM method on the target with the same name name as the event.\n\t\t\t\t// Don't do default actions on window, that's where global variables be (#6170)\n\t\t\t\tif ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) {\n\n\t\t\t\t\t// Don't re-trigger an onFOO event when we call its FOO() method\n\t\t\t\t\ttmp = elem[ ontype ];\n\n\t\t\t\t\tif ( tmp ) {\n\t\t\t\t\t\telem[ ontype ] = null;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Prevent re-triggering of the same event, since we already bubbled it above\n\t\t\t\t\tjQuery.event.triggered = type;\n\t\t\t\t\telem[ type ]();\n\t\t\t\t\tjQuery.event.triggered = undefined;\n\n\t\t\t\t\tif ( tmp ) {\n\t\t\t\t\t\telem[ ontype ] = tmp;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn event.result;\n\t},\n\n\tdispatch: function( event ) {\n\n\t\t// Make a writable jQuery.Event from the native event object\n\t\tevent = jQuery.event.fix( event );\n\n\t\tvar i, j, ret, matched, handleObj,\n\t\t\thandlerQueue = [],\n\t\t\targs = slice.call( arguments ),\n\t\t\thandlers = ( data_priv.get( this, \"events\" ) || {} )[ event.type ] || [],\n\t\t\tspecial = jQuery.event.special[ event.type ] || {};\n\n\t\t// Use the fix-ed jQuery.Event rather than the (read-only) native event\n\t\targs[0] = event;\n\t\tevent.delegateTarget = this;\n\n\t\t// Call the preDispatch hook for the mapped type, and let it bail if desired\n\t\tif ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Determine handlers\n\t\thandlerQueue = jQuery.event.handlers.call( this, event, handlers );\n\n\t\t// Run delegates first; they may want to stop propagation beneath us\n\t\ti = 0;\n\t\twhile ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {\n\t\t\tevent.currentTarget = matched.elem;\n\n\t\t\tj = 0;\n\t\t\twhile ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {\n\n\t\t\t\t// Triggered event must either 1) have no namespace, or 2) have namespace(s)\n\t\t\t\t// a subset or equal to those in the bound event (both can have no namespace).\n\t\t\t\tif ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {\n\n\t\t\t\t\tevent.handleObj = handleObj;\n\t\t\t\t\tevent.data = handleObj.data;\n\n\t\t\t\t\tret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )\n\t\t\t\t\t\t\t.apply( matched.elem, args );\n\n\t\t\t\t\tif ( ret !== undefined ) {\n\t\t\t\t\t\tif ( (event.result = ret) === false ) {\n\t\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Call the postDispatch hook for the mapped type\n\t\tif ( special.postDispatch ) {\n\t\t\tspecial.postDispatch.call( this, event );\n\t\t}\n\n\t\treturn event.result;\n\t},\n\n\thandlers: function( event, handlers ) {\n\t\tvar i, matches, sel, handleObj,\n\t\t\thandlerQueue = [],\n\t\t\tdelegateCount = handlers.delegateCount,\n\t\t\tcur = event.target;\n\n\t\t// Find delegate handlers\n\t\t// Black-hole SVG <use> instance trees (#13180)\n\t\t// Avoid non-left-click bubbling in Firefox (#3861)\n\t\tif ( delegateCount && cur.nodeType && (!event.button || event.type !== \"click\") ) {\n\n\t\t\tfor ( ; cur !== this; cur = cur.parentNode || this ) {\n\n\t\t\t\t// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)\n\t\t\t\tif ( cur.disabled !== true || event.type !== \"click\" ) {\n\t\t\t\t\tmatches = [];\n\t\t\t\t\tfor ( i = 0; i < delegateCount; i++ ) {\n\t\t\t\t\t\thandleObj = handlers[ i ];\n\n\t\t\t\t\t\t// Don't conflict with Object.prototype properties (#13203)\n\t\t\t\t\t\tsel = handleObj.selector + \" \";\n\n\t\t\t\t\t\tif ( matches[ sel ] === undefined ) {\n\t\t\t\t\t\t\tmatches[ sel ] = handleObj.needsContext ?\n\t\t\t\t\t\t\t\tjQuery( sel, this ).index( cur ) >= 0 :\n\t\t\t\t\t\t\t\tjQuery.find( sel, this, null, [ cur ] ).length;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( matches[ sel ] ) {\n\t\t\t\t\t\t\tmatches.push( handleObj );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif ( matches.length ) {\n\t\t\t\t\t\thandlerQueue.push({ elem: cur, handlers: matches });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Add the remaining (directly-bound) handlers\n\t\tif ( delegateCount < handlers.length ) {\n\t\t\thandlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });\n\t\t}\n\n\t\treturn handlerQueue;\n\t},\n\n\t// Includes some event props shared by KeyEvent and MouseEvent\n\tprops: \"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which\".split(\" \"),\n\n\tfixHooks: {},\n\n\tkeyHooks: {\n\t\tprops: \"char charCode key keyCode\".split(\" \"),\n\t\tfilter: function( event, original ) {\n\n\t\t\t// Add which for key events\n\t\t\tif ( event.which == null ) {\n\t\t\t\tevent.which = original.charCode != null ? original.charCode : original.keyCode;\n\t\t\t}\n\n\t\t\treturn event;\n\t\t}\n\t},\n\n\tmouseHooks: {\n\t\tprops: \"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement\".split(\" \"),\n\t\tfilter: function( event, original ) {\n\t\t\tvar eventDoc, doc, body,\n\t\t\t\tbutton = original.button;\n\n\t\t\t// Calculate pageX/Y if missing and clientX/Y available\n\t\t\tif ( event.pageX == null && original.clientX != null ) {\n\t\t\t\teventDoc = event.target.ownerDocument || document;\n\t\t\t\tdoc = eventDoc.documentElement;\n\t\t\t\tbody = eventDoc.body;\n\n\t\t\t\tevent.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );\n\t\t\t\tevent.pageY = original.clientY + ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - ( doc && doc.clientTop  || body && body.clientTop  || 0 );\n\t\t\t}\n\n\t\t\t// Add which for click: 1 === left; 2 === middle; 3 === right\n\t\t\t// Note: button is not normalized, so don't use it\n\t\t\tif ( !event.which && button !== undefined ) {\n\t\t\t\tevent.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );\n\t\t\t}\n\n\t\t\treturn event;\n\t\t}\n\t},\n\n\tfix: function( event ) {\n\t\tif ( event[ jQuery.expando ] ) {\n\t\t\treturn event;\n\t\t}\n\n\t\t// Create a writable copy of the event object and normalize some properties\n\t\tvar i, prop, copy,\n\t\t\ttype = event.type,\n\t\t\toriginalEvent = event,\n\t\t\tfixHook = this.fixHooks[ type ];\n\n\t\tif ( !fixHook ) {\n\t\t\tthis.fixHooks[ type ] = fixHook =\n\t\t\t\trmouseEvent.test( type ) ? this.mouseHooks :\n\t\t\t\trkeyEvent.test( type ) ? this.keyHooks :\n\t\t\t\t{};\n\t\t}\n\t\tcopy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;\n\n\t\tevent = new jQuery.Event( originalEvent );\n\n\t\ti = copy.length;\n\t\twhile ( i-- ) {\n\t\t\tprop = copy[ i ];\n\t\t\tevent[ prop ] = originalEvent[ prop ];\n\t\t}\n\n\t\t// Support: Cordova 2.5 (WebKit) (#13255)\n\t\t// All events should have a target; Cordova deviceready doesn't\n\t\tif ( !event.target ) {\n\t\t\tevent.target = document;\n\t\t}\n\n\t\t// Support: Safari 6.0+, Chrome<28\n\t\t// Target should not be a text node (#504, #13143)\n\t\tif ( event.target.nodeType === 3 ) {\n\t\t\tevent.target = event.target.parentNode;\n\t\t}\n\n\t\treturn fixHook.filter ? fixHook.filter( event, originalEvent ) : event;\n\t},\n\n\tspecial: {\n\t\tload: {\n\t\t\t// Prevent triggered image.load events from bubbling to window.load\n\t\t\tnoBubble: true\n\t\t},\n\t\tfocus: {\n\t\t\t// Fire native event if possible so blur/focus sequence is correct\n\t\t\ttrigger: function() {\n\t\t\t\tif ( this !== safeActiveElement() && this.focus ) {\n\t\t\t\t\tthis.focus();\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\t\t\tdelegateType: \"focusin\"\n\t\t},\n\t\tblur: {\n\t\t\ttrigger: function() {\n\t\t\t\tif ( this === safeActiveElement() && this.blur ) {\n\t\t\t\t\tthis.blur();\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\t\t\tdelegateType: \"focusout\"\n\t\t},\n\t\tclick: {\n\t\t\t// For checkbox, fire native event so checked state will be right\n\t\t\ttrigger: function() {\n\t\t\t\tif ( this.type === \"checkbox\" && this.click && jQuery.nodeName( this, \"input\" ) ) {\n\t\t\t\t\tthis.click();\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t// For cross-browser consistency, don't fire native .click() on links\n\t\t\t_default: function( event ) {\n\t\t\t\treturn jQuery.nodeName( event.target, \"a\" );\n\t\t\t}\n\t\t},\n\n\t\tbeforeunload: {\n\t\t\tpostDispatch: function( event ) {\n\n\t\t\t\t// Support: Firefox 20+\n\t\t\t\t// Firefox doesn't alert if the returnValue field is not set.\n\t\t\t\tif ( event.result !== undefined && event.originalEvent ) {\n\t\t\t\t\tevent.originalEvent.returnValue = event.result;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\tsimulate: function( type, elem, event, bubble ) {\n\t\t// Piggyback on a donor event to simulate a different one.\n\t\t// Fake originalEvent to avoid donor's stopPropagation, but if the\n\t\t// simulated event prevents default then we do the same on the donor.\n\t\tvar e = jQuery.extend(\n\t\t\tnew jQuery.Event(),\n\t\t\tevent,\n\t\t\t{\n\t\t\t\ttype: type,\n\t\t\t\tisSimulated: true,\n\t\t\t\toriginalEvent: {}\n\t\t\t}\n\t\t);\n\t\tif ( bubble ) {\n\t\t\tjQuery.event.trigger( e, null, elem );\n\t\t} else {\n\t\t\tjQuery.event.dispatch.call( elem, e );\n\t\t}\n\t\tif ( e.isDefaultPrevented() ) {\n\t\t\tevent.preventDefault();\n\t\t}\n\t}\n};\n\njQuery.removeEvent = function( elem, type, handle ) {\n\tif ( elem.removeEventListener ) {\n\t\telem.removeEventListener( type, handle, false );\n\t}\n};\n\njQuery.Event = function( src, props ) {\n\t// Allow instantiation without the 'new' keyword\n\tif ( !(this instanceof jQuery.Event) ) {\n\t\treturn new jQuery.Event( src, props );\n\t}\n\n\t// Event object\n\tif ( src && src.type ) {\n\t\tthis.originalEvent = src;\n\t\tthis.type = src.type;\n\n\t\t// Events bubbling up the document may have been marked as prevented\n\t\t// by a handler lower down the tree; reflect the correct value.\n\t\tthis.isDefaultPrevented = src.defaultPrevented ||\n\t\t\t\tsrc.defaultPrevented === undefined &&\n\t\t\t\t// Support: Android<4.0\n\t\t\t\tsrc.returnValue === false ?\n\t\t\treturnTrue :\n\t\t\treturnFalse;\n\n\t// Event type\n\t} else {\n\t\tthis.type = src;\n\t}\n\n\t// Put explicitly provided properties onto the event object\n\tif ( props ) {\n\t\tjQuery.extend( this, props );\n\t}\n\n\t// Create a timestamp if incoming event doesn't have one\n\tthis.timeStamp = src && src.timeStamp || jQuery.now();\n\n\t// Mark it as fixed\n\tthis[ jQuery.expando ] = true;\n};\n\n// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding\n// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html\njQuery.Event.prototype = {\n\tisDefaultPrevented: returnFalse,\n\tisPropagationStopped: returnFalse,\n\tisImmediatePropagationStopped: returnFalse,\n\n\tpreventDefault: function() {\n\t\tvar e = this.originalEvent;\n\n\t\tthis.isDefaultPrevented = returnTrue;\n\n\t\tif ( e && e.preventDefault ) {\n\t\t\te.preventDefault();\n\t\t}\n\t},\n\tstopPropagation: function() {\n\t\tvar e = this.originalEvent;\n\n\t\tthis.isPropagationStopped = returnTrue;\n\n\t\tif ( e && e.stopPropagation ) {\n\t\t\te.stopPropagation();\n\t\t}\n\t},\n\tstopImmediatePropagation: function() {\n\t\tvar e = this.originalEvent;\n\n\t\tthis.isImmediatePropagationStopped = returnTrue;\n\n\t\tif ( e && e.stopImmediatePropagation ) {\n\t\t\te.stopImmediatePropagation();\n\t\t}\n\n\t\tthis.stopPropagation();\n\t}\n};\n\n// Create mouseenter/leave events using mouseover/out and event-time checks\n// Support: Chrome 15+\njQuery.each({\n\tmouseenter: \"mouseover\",\n\tmouseleave: \"mouseout\",\n\tpointerenter: \"pointerover\",\n\tpointerleave: \"pointerout\"\n}, function( orig, fix ) {\n\tjQuery.event.special[ orig ] = {\n\t\tdelegateType: fix,\n\t\tbindType: fix,\n\n\t\thandle: function( event ) {\n\t\t\tvar ret,\n\t\t\t\ttarget = this,\n\t\t\t\trelated = event.relatedTarget,\n\t\t\t\thandleObj = event.handleObj;\n\n\t\t\t// For mousenter/leave call the handler if related is outside the target.\n\t\t\t// NB: No relatedTarget if the mouse left/entered the browser window\n\t\t\tif ( !related || (related !== target && !jQuery.contains( target, related )) ) {\n\t\t\t\tevent.type = handleObj.origType;\n\t\t\t\tret = handleObj.handler.apply( this, arguments );\n\t\t\t\tevent.type = fix;\n\t\t\t}\n\t\t\treturn ret;\n\t\t}\n\t};\n});\n\n// Support: Firefox, Chrome, Safari\n// Create \"bubbling\" focus and blur events\nif ( !support.focusinBubbles ) {\n\tjQuery.each({ focus: \"focusin\", blur: \"focusout\" }, function( orig, fix ) {\n\n\t\t// Attach a single capturing handler on the document while someone wants focusin/focusout\n\t\tvar handler = function( event ) {\n\t\t\t\tjQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );\n\t\t\t};\n\n\t\tjQuery.event.special[ fix ] = {\n\t\t\tsetup: function() {\n\t\t\t\tvar doc = this.ownerDocument || this,\n\t\t\t\t\tattaches = data_priv.access( doc, fix );\n\n\t\t\t\tif ( !attaches ) {\n\t\t\t\t\tdoc.addEventListener( orig, handler, true );\n\t\t\t\t}\n\t\t\t\tdata_priv.access( doc, fix, ( attaches || 0 ) + 1 );\n\t\t\t},\n\t\t\tteardown: function() {\n\t\t\t\tvar doc = this.ownerDocument || this,\n\t\t\t\t\tattaches = data_priv.access( doc, fix ) - 1;\n\n\t\t\t\tif ( !attaches ) {\n\t\t\t\t\tdoc.removeEventListener( orig, handler, true );\n\t\t\t\t\tdata_priv.remove( doc, fix );\n\n\t\t\t\t} else {\n\t\t\t\t\tdata_priv.access( doc, fix, attaches );\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t});\n}\n\njQuery.fn.extend({\n\n\ton: function( types, selector, data, fn, /*INTERNAL*/ one ) {\n\t\tvar origFn, type;\n\n\t\t// Types can be a map of types/handlers\n\t\tif ( typeof types === \"object\" ) {\n\t\t\t// ( types-Object, selector, data )\n\t\t\tif ( typeof selector !== \"string\" ) {\n\t\t\t\t// ( types-Object, data )\n\t\t\t\tdata = data || selector;\n\t\t\t\tselector = undefined;\n\t\t\t}\n\t\t\tfor ( type in types ) {\n\t\t\t\tthis.on( type, selector, data, types[ type ], one );\n\t\t\t}\n\t\t\treturn this;\n\t\t}\n\n\t\tif ( data == null && fn == null ) {\n\t\t\t// ( types, fn )\n\t\t\tfn = selector;\n\t\t\tdata = selector = undefined;\n\t\t} else if ( fn == null ) {\n\t\t\tif ( typeof selector === \"string\" ) {\n\t\t\t\t// ( types, selector, fn )\n\t\t\t\tfn = data;\n\t\t\t\tdata = undefined;\n\t\t\t} else {\n\t\t\t\t// ( types, data, fn )\n\t\t\t\tfn = data;\n\t\t\t\tdata = selector;\n\t\t\t\tselector = undefined;\n\t\t\t}\n\t\t}\n\t\tif ( fn === false ) {\n\t\t\tfn = returnFalse;\n\t\t} else if ( !fn ) {\n\t\t\treturn this;\n\t\t}\n\n\t\tif ( one === 1 ) {\n\t\t\torigFn = fn;\n\t\t\tfn = function( event ) {\n\t\t\t\t// Can use an empty set, since event contains the info\n\t\t\t\tjQuery().off( event );\n\t\t\t\treturn origFn.apply( this, arguments );\n\t\t\t};\n\t\t\t// Use same guid so caller can remove using origFn\n\t\t\tfn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );\n\t\t}\n\t\treturn this.each( function() {\n\t\t\tjQuery.event.add( this, types, fn, data, selector );\n\t\t});\n\t},\n\tone: function( types, selector, data, fn ) {\n\t\treturn this.on( types, selector, data, fn, 1 );\n\t},\n\toff: function( types, selector, fn ) {\n\t\tvar handleObj, type;\n\t\tif ( types && types.preventDefault && types.handleObj ) {\n\t\t\t// ( event )  dispatched jQuery.Event\n\t\t\thandleObj = types.handleObj;\n\t\t\tjQuery( types.delegateTarget ).off(\n\t\t\t\thandleObj.namespace ? handleObj.origType + \".\" + handleObj.namespace : handleObj.origType,\n\t\t\t\thandleObj.selector,\n\t\t\t\thandleObj.handler\n\t\t\t);\n\t\t\treturn this;\n\t\t}\n\t\tif ( typeof types === \"object\" ) {\n\t\t\t// ( types-object [, selector] )\n\t\t\tfor ( type in types ) {\n\t\t\t\tthis.off( type, selector, types[ type ] );\n\t\t\t}\n\t\t\treturn this;\n\t\t}\n\t\tif ( selector === false || typeof selector === \"function\" ) {\n\t\t\t// ( types [, fn] )\n\t\t\tfn = selector;\n\t\t\tselector = undefined;\n\t\t}\n\t\tif ( fn === false ) {\n\t\t\tfn = returnFalse;\n\t\t}\n\t\treturn this.each(function() {\n\t\t\tjQuery.event.remove( this, types, fn, selector );\n\t\t});\n\t},\n\n\ttrigger: function( type, data ) {\n\t\treturn this.each(function() {\n\t\t\tjQuery.event.trigger( type, data, this );\n\t\t});\n\t},\n\ttriggerHandler: function( type, data ) {\n\t\tvar elem = this[0];\n\t\tif ( elem ) {\n\t\t\treturn jQuery.event.trigger( type, data, elem, true );\n\t\t}\n\t}\n});\n\n\nvar\n\trxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\\w:]+)[^>]*)\\/>/gi,\n\trtagName = /<([\\w:]+)/,\n\trhtml = /<|&#?\\w+;/,\n\trnoInnerhtml = /<(?:script|style|link)/i,\n\t// checked=\"checked\" or checked\n\trchecked = /checked\\s*(?:[^=]|=\\s*.checked.)/i,\n\trscriptType = /^$|\\/(?:java|ecma)script/i,\n\trscriptTypeMasked = /^true\\/(.*)/,\n\trcleanScript = /^\\s*<!(?:\\[CDATA\\[|--)|(?:\\]\\]|--)>\\s*$/g,\n\n\t// We have to close these tags to support XHTML (#13200)\n\twrapMap = {\n\n\t\t// Support: IE9\n\t\toption: [ 1, \"<select multiple='multiple'>\", \"</select>\" ],\n\n\t\tthead: [ 1, \"<table>\", \"</table>\" ],\n\t\tcol: [ 2, \"<table><colgroup>\", \"</colgroup></table>\" ],\n\t\ttr: [ 2, \"<table><tbody>\", \"</tbody></table>\" ],\n\t\ttd: [ 3, \"<table><tbody><tr>\", \"</tr></tbody></table>\" ],\n\n\t\t_default: [ 0, \"\", \"\" ]\n\t};\n\n// Support: IE9\nwrapMap.optgroup = wrapMap.option;\n\nwrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;\nwrapMap.th = wrapMap.td;\n\n// Support: 1.x compatibility\n// Manipulating tables requires a tbody\nfunction manipulationTarget( elem, content ) {\n\treturn jQuery.nodeName( elem, \"table\" ) &&\n\t\tjQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, \"tr\" ) ?\n\n\t\telem.getElementsByTagName(\"tbody\")[0] ||\n\t\t\telem.appendChild( elem.ownerDocument.createElement(\"tbody\") ) :\n\t\telem;\n}\n\n// Replace/restore the type attribute of script elements for safe DOM manipulation\nfunction disableScript( elem ) {\n\telem.type = (elem.getAttribute(\"type\") !== null) + \"/\" + elem.type;\n\treturn elem;\n}\nfunction restoreScript( elem ) {\n\tvar match = rscriptTypeMasked.exec( elem.type );\n\n\tif ( match ) {\n\t\telem.type = match[ 1 ];\n\t} else {\n\t\telem.removeAttribute(\"type\");\n\t}\n\n\treturn elem;\n}\n\n// Mark scripts as having already been evaluated\nfunction setGlobalEval( elems, refElements ) {\n\tvar i = 0,\n\t\tl = elems.length;\n\n\tfor ( ; i < l; i++ ) {\n\t\tdata_priv.set(\n\t\t\telems[ i ], \"globalEval\", !refElements || data_priv.get( refElements[ i ], \"globalEval\" )\n\t\t);\n\t}\n}\n\nfunction cloneCopyEvent( src, dest ) {\n\tvar i, l, type, pdataOld, pdataCur, udataOld, udataCur, events;\n\n\tif ( dest.nodeType !== 1 ) {\n\t\treturn;\n\t}\n\n\t// 1. Copy private data: events, handlers, etc.\n\tif ( data_priv.hasData( src ) ) {\n\t\tpdataOld = data_priv.access( src );\n\t\tpdataCur = data_priv.set( dest, pdataOld );\n\t\tevents = pdataOld.events;\n\n\t\tif ( events ) {\n\t\t\tdelete pdataCur.handle;\n\t\t\tpdataCur.events = {};\n\n\t\t\tfor ( type in events ) {\n\t\t\t\tfor ( i = 0, l = events[ type ].length; i < l; i++ ) {\n\t\t\t\t\tjQuery.event.add( dest, type, events[ type ][ i ] );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// 2. Copy user data\n\tif ( data_user.hasData( src ) ) {\n\t\tudataOld = data_user.access( src );\n\t\tudataCur = jQuery.extend( {}, udataOld );\n\n\t\tdata_user.set( dest, udataCur );\n\t}\n}\n\nfunction getAll( context, tag ) {\n\tvar ret = context.getElementsByTagName ? context.getElementsByTagName( tag || \"*\" ) :\n\t\t\tcontext.querySelectorAll ? context.querySelectorAll( tag || \"*\" ) :\n\t\t\t[];\n\n\treturn tag === undefined || tag && jQuery.nodeName( context, tag ) ?\n\t\tjQuery.merge( [ context ], ret ) :\n\t\tret;\n}\n\n// Fix IE bugs, see support tests\nfunction fixInput( src, dest ) {\n\tvar nodeName = dest.nodeName.toLowerCase();\n\n\t// Fails to persist the checked state of a cloned checkbox or radio button.\n\tif ( nodeName === \"input\" && rcheckableType.test( src.type ) ) {\n\t\tdest.checked = src.checked;\n\n\t// Fails to return the selected option to the default selected state when cloning options\n\t} else if ( nodeName === \"input\" || nodeName === \"textarea\" ) {\n\t\tdest.defaultValue = src.defaultValue;\n\t}\n}\n\njQuery.extend({\n\tclone: function( elem, dataAndEvents, deepDataAndEvents ) {\n\t\tvar i, l, srcElements, destElements,\n\t\t\tclone = elem.cloneNode( true ),\n\t\t\tinPage = jQuery.contains( elem.ownerDocument, elem );\n\n\t\t// Fix IE cloning issues\n\t\tif ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&\n\t\t\t\t!jQuery.isXMLDoc( elem ) ) {\n\n\t\t\t// We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2\n\t\t\tdestElements = getAll( clone );\n\t\t\tsrcElements = getAll( elem );\n\n\t\t\tfor ( i = 0, l = srcElements.length; i < l; i++ ) {\n\t\t\t\tfixInput( srcElements[ i ], destElements[ i ] );\n\t\t\t}\n\t\t}\n\n\t\t// Copy the events from the original to the clone\n\t\tif ( dataAndEvents ) {\n\t\t\tif ( deepDataAndEvents ) {\n\t\t\t\tsrcElements = srcElements || getAll( elem );\n\t\t\t\tdestElements = destElements || getAll( clone );\n\n\t\t\t\tfor ( i = 0, l = srcElements.length; i < l; i++ ) {\n\t\t\t\t\tcloneCopyEvent( srcElements[ i ], destElements[ i ] );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcloneCopyEvent( elem, clone );\n\t\t\t}\n\t\t}\n\n\t\t// Preserve script evaluation history\n\t\tdestElements = getAll( clone, \"script\" );\n\t\tif ( destElements.length > 0 ) {\n\t\t\tsetGlobalEval( destElements, !inPage && getAll( elem, \"script\" ) );\n\t\t}\n\n\t\t// Return the cloned set\n\t\treturn clone;\n\t},\n\n\tbuildFragment: function( elems, context, scripts, selection ) {\n\t\tvar elem, tmp, tag, wrap, contains, j,\n\t\t\tfragment = context.createDocumentFragment(),\n\t\t\tnodes = [],\n\t\t\ti = 0,\n\t\t\tl = elems.length;\n\n\t\tfor ( ; i < l; i++ ) {\n\t\t\telem = elems[ i ];\n\n\t\t\tif ( elem || elem === 0 ) {\n\n\t\t\t\t// Add nodes directly\n\t\t\t\tif ( jQuery.type( elem ) === \"object\" ) {\n\t\t\t\t\t// Support: QtWebKit, PhantomJS\n\t\t\t\t\t// push.apply(_, arraylike) throws on ancient WebKit\n\t\t\t\t\tjQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );\n\n\t\t\t\t// Convert non-html into a text node\n\t\t\t\t} else if ( !rhtml.test( elem ) ) {\n\t\t\t\t\tnodes.push( context.createTextNode( elem ) );\n\n\t\t\t\t// Convert html into DOM nodes\n\t\t\t\t} else {\n\t\t\t\t\ttmp = tmp || fragment.appendChild( context.createElement(\"div\") );\n\n\t\t\t\t\t// Deserialize a standard representation\n\t\t\t\t\ttag = ( rtagName.exec( elem ) || [ \"\", \"\" ] )[ 1 ].toLowerCase();\n\t\t\t\t\twrap = wrapMap[ tag ] || wrapMap._default;\n\t\t\t\t\ttmp.innerHTML = wrap[ 1 ] + elem.replace( rxhtmlTag, \"<$1></$2>\" ) + wrap[ 2 ];\n\n\t\t\t\t\t// Descend through wrappers to the right content\n\t\t\t\t\tj = wrap[ 0 ];\n\t\t\t\t\twhile ( j-- ) {\n\t\t\t\t\t\ttmp = tmp.lastChild;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Support: QtWebKit, PhantomJS\n\t\t\t\t\t// push.apply(_, arraylike) throws on ancient WebKit\n\t\t\t\t\tjQuery.merge( nodes, tmp.childNodes );\n\n\t\t\t\t\t// Remember the top-level container\n\t\t\t\t\ttmp = fragment.firstChild;\n\n\t\t\t\t\t// Ensure the created nodes are orphaned (#12392)\n\t\t\t\t\ttmp.textContent = \"\";\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Remove wrapper from fragment\n\t\tfragment.textContent = \"\";\n\n\t\ti = 0;\n\t\twhile ( (elem = nodes[ i++ ]) ) {\n\n\t\t\t// #4087 - If origin and destination elements are the same, and this is\n\t\t\t// that element, do not do anything\n\t\t\tif ( selection && jQuery.inArray( elem, selection ) !== -1 ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tcontains = jQuery.contains( elem.ownerDocument, elem );\n\n\t\t\t// Append to fragment\n\t\t\ttmp = getAll( fragment.appendChild( elem ), \"script\" );\n\n\t\t\t// Preserve script evaluation history\n\t\t\tif ( contains ) {\n\t\t\t\tsetGlobalEval( tmp );\n\t\t\t}\n\n\t\t\t// Capture executables\n\t\t\tif ( scripts ) {\n\t\t\t\tj = 0;\n\t\t\t\twhile ( (elem = tmp[ j++ ]) ) {\n\t\t\t\t\tif ( rscriptType.test( elem.type || \"\" ) ) {\n\t\t\t\t\t\tscripts.push( elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn fragment;\n\t},\n\n\tcleanData: function( elems ) {\n\t\tvar data, elem, type, key,\n\t\t\tspecial = jQuery.event.special,\n\t\t\ti = 0;\n\n\t\tfor ( ; (elem = elems[ i ]) !== undefined; i++ ) {\n\t\t\tif ( jQuery.acceptData( elem ) ) {\n\t\t\t\tkey = elem[ data_priv.expando ];\n\n\t\t\t\tif ( key && (data = data_priv.cache[ key ]) ) {\n\t\t\t\t\tif ( data.events ) {\n\t\t\t\t\t\tfor ( type in data.events ) {\n\t\t\t\t\t\t\tif ( special[ type ] ) {\n\t\t\t\t\t\t\t\tjQuery.event.remove( elem, type );\n\n\t\t\t\t\t\t\t// This is a shortcut to avoid jQuery.event.remove's overhead\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tjQuery.removeEvent( elem, type, data.handle );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif ( data_priv.cache[ key ] ) {\n\t\t\t\t\t\t// Discard any remaining `private` data\n\t\t\t\t\t\tdelete data_priv.cache[ key ];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Discard any remaining `user` data\n\t\t\tdelete data_user.cache[ elem[ data_user.expando ] ];\n\t\t}\n\t}\n});\n\njQuery.fn.extend({\n\ttext: function( value ) {\n\t\treturn access( this, function( value ) {\n\t\t\treturn value === undefined ?\n\t\t\t\tjQuery.text( this ) :\n\t\t\t\tthis.empty().each(function() {\n\t\t\t\t\tif ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {\n\t\t\t\t\t\tthis.textContent = value;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t}, null, value, arguments.length );\n\t},\n\n\tappend: function() {\n\t\treturn this.domManip( arguments, function( elem ) {\n\t\t\tif ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {\n\t\t\t\tvar target = manipulationTarget( this, elem );\n\t\t\t\ttarget.appendChild( elem );\n\t\t\t}\n\t\t});\n\t},\n\n\tprepend: function() {\n\t\treturn this.domManip( arguments, function( elem ) {\n\t\t\tif ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {\n\t\t\t\tvar target = manipulationTarget( this, elem );\n\t\t\t\ttarget.insertBefore( elem, target.firstChild );\n\t\t\t}\n\t\t});\n\t},\n\n\tbefore: function() {\n\t\treturn this.domManip( arguments, function( elem ) {\n\t\t\tif ( this.parentNode ) {\n\t\t\t\tthis.parentNode.insertBefore( elem, this );\n\t\t\t}\n\t\t});\n\t},\n\n\tafter: function() {\n\t\treturn this.domManip( arguments, function( elem ) {\n\t\t\tif ( this.parentNode ) {\n\t\t\t\tthis.parentNode.insertBefore( elem, this.nextSibling );\n\t\t\t}\n\t\t});\n\t},\n\n\tremove: function( selector, keepData /* Internal Use Only */ ) {\n\t\tvar elem,\n\t\t\telems = selector ? jQuery.filter( selector, this ) : this,\n\t\t\ti = 0;\n\n\t\tfor ( ; (elem = elems[i]) != null; i++ ) {\n\t\t\tif ( !keepData && elem.nodeType === 1 ) {\n\t\t\t\tjQuery.cleanData( getAll( elem ) );\n\t\t\t}\n\n\t\t\tif ( elem.parentNode ) {\n\t\t\t\tif ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) {\n\t\t\t\t\tsetGlobalEval( getAll( elem, \"script\" ) );\n\t\t\t\t}\n\t\t\t\telem.parentNode.removeChild( elem );\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tempty: function() {\n\t\tvar elem,\n\t\t\ti = 0;\n\n\t\tfor ( ; (elem = this[i]) != null; i++ ) {\n\t\t\tif ( elem.nodeType === 1 ) {\n\n\t\t\t\t// Prevent memory leaks\n\t\t\t\tjQuery.cleanData( getAll( elem, false ) );\n\n\t\t\t\t// Remove any remaining nodes\n\t\t\t\telem.textContent = \"\";\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tclone: function( dataAndEvents, deepDataAndEvents ) {\n\t\tdataAndEvents = dataAndEvents == null ? false : dataAndEvents;\n\t\tdeepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;\n\n\t\treturn this.map(function() {\n\t\t\treturn jQuery.clone( this, dataAndEvents, deepDataAndEvents );\n\t\t});\n\t},\n\n\thtml: function( value ) {\n\t\treturn access( this, function( value ) {\n\t\t\tvar elem = this[ 0 ] || {},\n\t\t\t\ti = 0,\n\t\t\t\tl = this.length;\n\n\t\t\tif ( value === undefined && elem.nodeType === 1 ) {\n\t\t\t\treturn elem.innerHTML;\n\t\t\t}\n\n\t\t\t// See if we can take a shortcut and just use innerHTML\n\t\t\tif ( typeof value === \"string\" && !rnoInnerhtml.test( value ) &&\n\t\t\t\t!wrapMap[ ( rtagName.exec( value ) || [ \"\", \"\" ] )[ 1 ].toLowerCase() ] ) {\n\n\t\t\t\tvalue = value.replace( rxhtmlTag, \"<$1></$2>\" );\n\n\t\t\t\ttry {\n\t\t\t\t\tfor ( ; i < l; i++ ) {\n\t\t\t\t\t\telem = this[ i ] || {};\n\n\t\t\t\t\t\t// Remove element nodes and prevent memory leaks\n\t\t\t\t\t\tif ( elem.nodeType === 1 ) {\n\t\t\t\t\t\t\tjQuery.cleanData( getAll( elem, false ) );\n\t\t\t\t\t\t\telem.innerHTML = value;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\telem = 0;\n\n\t\t\t\t// If using innerHTML throws an exception, use the fallback method\n\t\t\t\t} catch( e ) {}\n\t\t\t}\n\n\t\t\tif ( elem ) {\n\t\t\t\tthis.empty().append( value );\n\t\t\t}\n\t\t}, null, value, arguments.length );\n\t},\n\n\treplaceWith: function() {\n\t\tvar arg = arguments[ 0 ];\n\n\t\t// Make the changes, replacing each context element with the new content\n\t\tthis.domManip( arguments, function( elem ) {\n\t\t\targ = this.parentNode;\n\n\t\t\tjQuery.cleanData( getAll( this ) );\n\n\t\t\tif ( arg ) {\n\t\t\t\targ.replaceChild( elem, this );\n\t\t\t}\n\t\t});\n\n\t\t// Force removal if there was no new content (e.g., from empty arguments)\n\t\treturn arg && (arg.length || arg.nodeType) ? this : this.remove();\n\t},\n\n\tdetach: function( selector ) {\n\t\treturn this.remove( selector, true );\n\t},\n\n\tdomManip: function( args, callback ) {\n\n\t\t// Flatten any nested arrays\n\t\targs = concat.apply( [], args );\n\n\t\tvar fragment, first, scripts, hasScripts, node, doc,\n\t\t\ti = 0,\n\t\t\tl = this.length,\n\t\t\tset = this,\n\t\t\tiNoClone = l - 1,\n\t\t\tvalue = args[ 0 ],\n\t\t\tisFunction = jQuery.isFunction( value );\n\n\t\t// We can't cloneNode fragments that contain checked, in WebKit\n\t\tif ( isFunction ||\n\t\t\t\t( l > 1 && typeof value === \"string\" &&\n\t\t\t\t\t!support.checkClone && rchecked.test( value ) ) ) {\n\t\t\treturn this.each(function( index ) {\n\t\t\t\tvar self = set.eq( index );\n\t\t\t\tif ( isFunction ) {\n\t\t\t\t\targs[ 0 ] = value.call( this, index, self.html() );\n\t\t\t\t}\n\t\t\t\tself.domManip( args, callback );\n\t\t\t});\n\t\t}\n\n\t\tif ( l ) {\n\t\t\tfragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this );\n\t\t\tfirst = fragment.firstChild;\n\n\t\t\tif ( fragment.childNodes.length === 1 ) {\n\t\t\t\tfragment = first;\n\t\t\t}\n\n\t\t\tif ( first ) {\n\t\t\t\tscripts = jQuery.map( getAll( fragment, \"script\" ), disableScript );\n\t\t\t\thasScripts = scripts.length;\n\n\t\t\t\t// Use the original fragment for the last item instead of the first because it can end up\n\t\t\t\t// being emptied incorrectly in certain situations (#8070).\n\t\t\t\tfor ( ; i < l; i++ ) {\n\t\t\t\t\tnode = fragment;\n\n\t\t\t\t\tif ( i !== iNoClone ) {\n\t\t\t\t\t\tnode = jQuery.clone( node, true, true );\n\n\t\t\t\t\t\t// Keep references to cloned scripts for later restoration\n\t\t\t\t\t\tif ( hasScripts ) {\n\t\t\t\t\t\t\t// Support: QtWebKit\n\t\t\t\t\t\t\t// jQuery.merge because push.apply(_, arraylike) throws\n\t\t\t\t\t\t\tjQuery.merge( scripts, getAll( node, \"script\" ) );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tcallback.call( this[ i ], node, i );\n\t\t\t\t}\n\n\t\t\t\tif ( hasScripts ) {\n\t\t\t\t\tdoc = scripts[ scripts.length - 1 ].ownerDocument;\n\n\t\t\t\t\t// Reenable scripts\n\t\t\t\t\tjQuery.map( scripts, restoreScript );\n\n\t\t\t\t\t// Evaluate executable scripts on first document insertion\n\t\t\t\t\tfor ( i = 0; i < hasScripts; i++ ) {\n\t\t\t\t\t\tnode = scripts[ i ];\n\t\t\t\t\t\tif ( rscriptType.test( node.type || \"\" ) &&\n\t\t\t\t\t\t\t!data_priv.access( node, \"globalEval\" ) && jQuery.contains( doc, node ) ) {\n\n\t\t\t\t\t\t\tif ( node.src ) {\n\t\t\t\t\t\t\t\t// Optional AJAX dependency, but won't run scripts if not present\n\t\t\t\t\t\t\t\tif ( jQuery._evalUrl ) {\n\t\t\t\t\t\t\t\t\tjQuery._evalUrl( node.src );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tjQuery.globalEval( node.textContent.replace( rcleanScript, \"\" ) );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t}\n});\n\njQuery.each({\n\tappendTo: \"append\",\n\tprependTo: \"prepend\",\n\tinsertBefore: \"before\",\n\tinsertAfter: \"after\",\n\treplaceAll: \"replaceWith\"\n}, function( name, original ) {\n\tjQuery.fn[ name ] = function( selector ) {\n\t\tvar elems,\n\t\t\tret = [],\n\t\t\tinsert = jQuery( selector ),\n\t\t\tlast = insert.length - 1,\n\t\t\ti = 0;\n\n\t\tfor ( ; i <= last; i++ ) {\n\t\t\telems = i === last ? this : this.clone( true );\n\t\t\tjQuery( insert[ i ] )[ original ]( elems );\n\n\t\t\t// Support: QtWebKit\n\t\t\t// .get() because push.apply(_, arraylike) throws\n\t\t\tpush.apply( ret, elems.get() );\n\t\t}\n\n\t\treturn this.pushStack( ret );\n\t};\n});\n\n\nvar iframe,\n\telemdisplay = {};\n\n/**\n * Retrieve the actual display of a element\n * @param {String} name nodeName of the element\n * @param {Object} doc Document object\n */\n// Called only from within defaultDisplay\nfunction actualDisplay( name, doc ) {\n\tvar style,\n\t\telem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),\n\n\t\t// getDefaultComputedStyle might be reliably used only on attached element\n\t\tdisplay = window.getDefaultComputedStyle && ( style = window.getDefaultComputedStyle( elem[ 0 ] ) ) ?\n\n\t\t\t// Use of this method is a temporary fix (more like optimization) until something better comes along,\n\t\t\t// since it was removed from specification and supported only in FF\n\t\t\tstyle.display : jQuery.css( elem[ 0 ], \"display\" );\n\n\t// We don't have any data stored on the element,\n\t// so use \"detach\" method as fast way to get rid of the element\n\telem.detach();\n\n\treturn display;\n}\n\n/**\n * Try to determine the default display value of an element\n * @param {String} nodeName\n */\nfunction defaultDisplay( nodeName ) {\n\tvar doc = document,\n\t\tdisplay = elemdisplay[ nodeName ];\n\n\tif ( !display ) {\n\t\tdisplay = actualDisplay( nodeName, doc );\n\n\t\t// If the simple way fails, read from inside an iframe\n\t\tif ( display === \"none\" || !display ) {\n\n\t\t\t// Use the already-created iframe if possible\n\t\t\tiframe = (iframe || jQuery( \"<iframe frameborder='0' width='0' height='0'/>\" )).appendTo( doc.documentElement );\n\n\t\t\t// Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse\n\t\t\tdoc = iframe[ 0 ].contentDocument;\n\n\t\t\t// Support: IE\n\t\t\tdoc.write();\n\t\t\tdoc.close();\n\n\t\t\tdisplay = actualDisplay( nodeName, doc );\n\t\t\tiframe.detach();\n\t\t}\n\n\t\t// Store the correct default display\n\t\telemdisplay[ nodeName ] = display;\n\t}\n\n\treturn display;\n}\nvar rmargin = (/^margin/);\n\nvar rnumnonpx = new RegExp( \"^(\" + pnum + \")(?!px)[a-z%]+$\", \"i\" );\n\nvar getStyles = function( elem ) {\n\t\t// Support: IE<=11+, Firefox<=30+ (#15098, #14150)\n\t\t// IE throws on elements created in popups\n\t\t// FF meanwhile throws on frame elements through \"defaultView.getComputedStyle\"\n\t\tif ( elem.ownerDocument.defaultView.opener ) {\n\t\t\treturn elem.ownerDocument.defaultView.getComputedStyle( elem, null );\n\t\t}\n\n\t\treturn window.getComputedStyle( elem, null );\n\t};\n\n\n\nfunction curCSS( elem, name, computed ) {\n\tvar width, minWidth, maxWidth, ret,\n\t\tstyle = elem.style;\n\n\tcomputed = computed || getStyles( elem );\n\n\t// Support: IE9\n\t// getPropertyValue is only needed for .css('filter') (#12537)\n\tif ( computed ) {\n\t\tret = computed.getPropertyValue( name ) || computed[ name ];\n\t}\n\n\tif ( computed ) {\n\n\t\tif ( ret === \"\" && !jQuery.contains( elem.ownerDocument, elem ) ) {\n\t\t\tret = jQuery.style( elem, name );\n\t\t}\n\n\t\t// Support: iOS < 6\n\t\t// A tribute to the \"awesome hack by Dean Edwards\"\n\t\t// iOS < 6 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels\n\t\t// this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values\n\t\tif ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {\n\n\t\t\t// Remember the original values\n\t\t\twidth = style.width;\n\t\t\tminWidth = style.minWidth;\n\t\t\tmaxWidth = style.maxWidth;\n\n\t\t\t// Put in the new values to get a computed value out\n\t\t\tstyle.minWidth = style.maxWidth = style.width = ret;\n\t\t\tret = computed.width;\n\n\t\t\t// Revert the changed values\n\t\t\tstyle.width = width;\n\t\t\tstyle.minWidth = minWidth;\n\t\t\tstyle.maxWidth = maxWidth;\n\t\t}\n\t}\n\n\treturn ret !== undefined ?\n\t\t// Support: IE\n\t\t// IE returns zIndex value as an integer.\n\t\tret + \"\" :\n\t\tret;\n}\n\n\nfunction addGetHookIf( conditionFn, hookFn ) {\n\t// Define the hook, we'll check on the first run if it's really needed.\n\treturn {\n\t\tget: function() {\n\t\t\tif ( conditionFn() ) {\n\t\t\t\t// Hook not needed (or it's not possible to use it due\n\t\t\t\t// to missing dependency), remove it.\n\t\t\t\tdelete this.get;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Hook needed; redefine it so that the support test is not executed again.\n\t\t\treturn (this.get = hookFn).apply( this, arguments );\n\t\t}\n\t};\n}\n\n\n(function() {\n\tvar pixelPositionVal, boxSizingReliableVal,\n\t\tdocElem = document.documentElement,\n\t\tcontainer = document.createElement( \"div\" ),\n\t\tdiv = document.createElement( \"div\" );\n\n\tif ( !div.style ) {\n\t\treturn;\n\t}\n\n\t// Support: IE9-11+\n\t// Style of cloned element affects source element cloned (#8908)\n\tdiv.style.backgroundClip = \"content-box\";\n\tdiv.cloneNode( true ).style.backgroundClip = \"\";\n\tsupport.clearCloneStyle = div.style.backgroundClip === \"content-box\";\n\n\tcontainer.style.cssText = \"border:0;width:0;height:0;top:0;left:-9999px;margin-top:1px;\" +\n\t\t\"position:absolute\";\n\tcontainer.appendChild( div );\n\n\t// Executing both pixelPosition & boxSizingReliable tests require only one layout\n\t// so they're executed at the same time to save the second computation.\n\tfunction computePixelPositionAndBoxSizingReliable() {\n\t\tdiv.style.cssText =\n\t\t\t// Support: Firefox<29, Android 2.3\n\t\t\t// Vendor-prefix box-sizing\n\t\t\t\"-webkit-box-sizing:border-box;-moz-box-sizing:border-box;\" +\n\t\t\t\"box-sizing:border-box;display:block;margin-top:1%;top:1%;\" +\n\t\t\t\"border:1px;padding:1px;width:4px;position:absolute\";\n\t\tdiv.innerHTML = \"\";\n\t\tdocElem.appendChild( container );\n\n\t\tvar divStyle = window.getComputedStyle( div, null );\n\t\tpixelPositionVal = divStyle.top !== \"1%\";\n\t\tboxSizingReliableVal = divStyle.width === \"4px\";\n\n\t\tdocElem.removeChild( container );\n\t}\n\n\t// Support: node.js jsdom\n\t// Don't assume that getComputedStyle is a property of the global object\n\tif ( window.getComputedStyle ) {\n\t\tjQuery.extend( support, {\n\t\t\tpixelPosition: function() {\n\n\t\t\t\t// This test is executed only once but we still do memoizing\n\t\t\t\t// since we can use the boxSizingReliable pre-computing.\n\t\t\t\t// No need to check if the test was already performed, though.\n\t\t\t\tcomputePixelPositionAndBoxSizingReliable();\n\t\t\t\treturn pixelPositionVal;\n\t\t\t},\n\t\t\tboxSizingReliable: function() {\n\t\t\t\tif ( boxSizingReliableVal == null ) {\n\t\t\t\t\tcomputePixelPositionAndBoxSizingReliable();\n\t\t\t\t}\n\t\t\t\treturn boxSizingReliableVal;\n\t\t\t},\n\t\t\treliableMarginRight: function() {\n\n\t\t\t\t// Support: Android 2.3\n\t\t\t\t// Check if div with explicit width and no margin-right incorrectly\n\t\t\t\t// gets computed margin-right based on width of container. (#3333)\n\t\t\t\t// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right\n\t\t\t\t// This support function is only executed once so no memoizing is needed.\n\t\t\t\tvar ret,\n\t\t\t\t\tmarginDiv = div.appendChild( document.createElement( \"div\" ) );\n\n\t\t\t\t// Reset CSS: box-sizing; display; margin; border; padding\n\t\t\t\tmarginDiv.style.cssText = div.style.cssText =\n\t\t\t\t\t// Support: Firefox<29, Android 2.3\n\t\t\t\t\t// Vendor-prefix box-sizing\n\t\t\t\t\t\"-webkit-box-sizing:content-box;-moz-box-sizing:content-box;\" +\n\t\t\t\t\t\"box-sizing:content-box;display:block;margin:0;border:0;padding:0\";\n\t\t\t\tmarginDiv.style.marginRight = marginDiv.style.width = \"0\";\n\t\t\t\tdiv.style.width = \"1px\";\n\t\t\t\tdocElem.appendChild( container );\n\n\t\t\t\tret = !parseFloat( window.getComputedStyle( marginDiv, null ).marginRight );\n\n\t\t\t\tdocElem.removeChild( container );\n\t\t\t\tdiv.removeChild( marginDiv );\n\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t});\n\t}\n})();\n\n\n// A method for quickly swapping in/out CSS properties to get correct calculations.\njQuery.swap = function( elem, options, callback, args ) {\n\tvar ret, name,\n\t\told = {};\n\n\t// Remember the old values, and insert the new ones\n\tfor ( name in options ) {\n\t\told[ name ] = elem.style[ name ];\n\t\telem.style[ name ] = options[ name ];\n\t}\n\n\tret = callback.apply( elem, args || [] );\n\n\t// Revert the old values\n\tfor ( name in options ) {\n\t\telem.style[ name ] = old[ name ];\n\t}\n\n\treturn ret;\n};\n\n\nvar\n\t// Swappable if display is none or starts with table except \"table\", \"table-cell\", or \"table-caption\"\n\t// See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display\n\trdisplayswap = /^(none|table(?!-c[ea]).+)/,\n\trnumsplit = new RegExp( \"^(\" + pnum + \")(.*)$\", \"i\" ),\n\trrelNum = new RegExp( \"^([+-])=(\" + pnum + \")\", \"i\" ),\n\n\tcssShow = { position: \"absolute\", visibility: \"hidden\", display: \"block\" },\n\tcssNormalTransform = {\n\t\tletterSpacing: \"0\",\n\t\tfontWeight: \"400\"\n\t},\n\n\tcssPrefixes = [ \"Webkit\", \"O\", \"Moz\", \"ms\" ];\n\n// Return a css property mapped to a potentially vendor prefixed property\nfunction vendorPropName( style, name ) {\n\n\t// Shortcut for names that are not vendor prefixed\n\tif ( name in style ) {\n\t\treturn name;\n\t}\n\n\t// Check for vendor prefixed names\n\tvar capName = name[0].toUpperCase() + name.slice(1),\n\t\torigName = name,\n\t\ti = cssPrefixes.length;\n\n\twhile ( i-- ) {\n\t\tname = cssPrefixes[ i ] + capName;\n\t\tif ( name in style ) {\n\t\t\treturn name;\n\t\t}\n\t}\n\n\treturn origName;\n}\n\nfunction setPositiveNumber( elem, value, subtract ) {\n\tvar matches = rnumsplit.exec( value );\n\treturn matches ?\n\t\t// Guard against undefined \"subtract\", e.g., when used as in cssHooks\n\t\tMath.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || \"px\" ) :\n\t\tvalue;\n}\n\nfunction augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {\n\tvar i = extra === ( isBorderBox ? \"border\" : \"content\" ) ?\n\t\t// If we already have the right measurement, avoid augmentation\n\t\t4 :\n\t\t// Otherwise initialize for horizontal or vertical properties\n\t\tname === \"width\" ? 1 : 0,\n\n\t\tval = 0;\n\n\tfor ( ; i < 4; i += 2 ) {\n\t\t// Both box models exclude margin, so add it if we want it\n\t\tif ( extra === \"margin\" ) {\n\t\t\tval += jQuery.css( elem, extra + cssExpand[ i ], true, styles );\n\t\t}\n\n\t\tif ( isBorderBox ) {\n\t\t\t// border-box includes padding, so remove it if we want content\n\t\t\tif ( extra === \"content\" ) {\n\t\t\t\tval -= jQuery.css( elem, \"padding\" + cssExpand[ i ], true, styles );\n\t\t\t}\n\n\t\t\t// At this point, extra isn't border nor margin, so remove border\n\t\t\tif ( extra !== \"margin\" ) {\n\t\t\t\tval -= jQuery.css( elem, \"border\" + cssExpand[ i ] + \"Width\", true, styles );\n\t\t\t}\n\t\t} else {\n\t\t\t// At this point, extra isn't content, so add padding\n\t\t\tval += jQuery.css( elem, \"padding\" + cssExpand[ i ], true, styles );\n\n\t\t\t// At this point, extra isn't content nor padding, so add border\n\t\t\tif ( extra !== \"padding\" ) {\n\t\t\t\tval += jQuery.css( elem, \"border\" + cssExpand[ i ] + \"Width\", true, styles );\n\t\t\t}\n\t\t}\n\t}\n\n\treturn val;\n}\n\nfunction getWidthOrHeight( elem, name, extra ) {\n\n\t// Start with offset property, which is equivalent to the border-box value\n\tvar valueIsBorderBox = true,\n\t\tval = name === \"width\" ? elem.offsetWidth : elem.offsetHeight,\n\t\tstyles = getStyles( elem ),\n\t\tisBorderBox = jQuery.css( elem, \"boxSizing\", false, styles ) === \"border-box\";\n\n\t// Some non-html elements return undefined for offsetWidth, so check for null/undefined\n\t// svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285\n\t// MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668\n\tif ( val <= 0 || val == null ) {\n\t\t// Fall back to computed then uncomputed css if necessary\n\t\tval = curCSS( elem, name, styles );\n\t\tif ( val < 0 || val == null ) {\n\t\t\tval = elem.style[ name ];\n\t\t}\n\n\t\t// Computed unit is not pixels. Stop here and return.\n\t\tif ( rnumnonpx.test(val) ) {\n\t\t\treturn val;\n\t\t}\n\n\t\t// Check for style in case a browser which returns unreliable values\n\t\t// for getComputedStyle silently falls back to the reliable elem.style\n\t\tvalueIsBorderBox = isBorderBox &&\n\t\t\t( support.boxSizingReliable() || val === elem.style[ name ] );\n\n\t\t// Normalize \"\", auto, and prepare for extra\n\t\tval = parseFloat( val ) || 0;\n\t}\n\n\t// Use the active box-sizing model to add/subtract irrelevant styles\n\treturn ( val +\n\t\taugmentWidthOrHeight(\n\t\t\telem,\n\t\t\tname,\n\t\t\textra || ( isBorderBox ? \"border\" : \"content\" ),\n\t\t\tvalueIsBorderBox,\n\t\t\tstyles\n\t\t)\n\t) + \"px\";\n}\n\nfunction showHide( elements, show ) {\n\tvar display, elem, hidden,\n\t\tvalues = [],\n\t\tindex = 0,\n\t\tlength = elements.length;\n\n\tfor ( ; index < length; index++ ) {\n\t\telem = elements[ index ];\n\t\tif ( !elem.style ) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tvalues[ index ] = data_priv.get( elem, \"olddisplay\" );\n\t\tdisplay = elem.style.display;\n\t\tif ( show ) {\n\t\t\t// Reset the inline display of this element to learn if it is\n\t\t\t// being hidden by cascaded rules or not\n\t\t\tif ( !values[ index ] && display === \"none\" ) {\n\t\t\t\telem.style.display = \"\";\n\t\t\t}\n\n\t\t\t// Set elements which have been overridden with display: none\n\t\t\t// in a stylesheet to whatever the default browser style is\n\t\t\t// for such an element\n\t\t\tif ( elem.style.display === \"\" && isHidden( elem ) ) {\n\t\t\t\tvalues[ index ] = data_priv.access( elem, \"olddisplay\", defaultDisplay(elem.nodeName) );\n\t\t\t}\n\t\t} else {\n\t\t\thidden = isHidden( elem );\n\n\t\t\tif ( display !== \"none\" || !hidden ) {\n\t\t\t\tdata_priv.set( elem, \"olddisplay\", hidden ? display : jQuery.css( elem, \"display\" ) );\n\t\t\t}\n\t\t}\n\t}\n\n\t// Set the display of most of the elements in a second loop\n\t// to avoid the constant reflow\n\tfor ( index = 0; index < length; index++ ) {\n\t\telem = elements[ index ];\n\t\tif ( !elem.style ) {\n\t\t\tcontinue;\n\t\t}\n\t\tif ( !show || elem.style.display === \"none\" || elem.style.display === \"\" ) {\n\t\t\telem.style.display = show ? values[ index ] || \"\" : \"none\";\n\t\t}\n\t}\n\n\treturn elements;\n}\n\njQuery.extend({\n\n\t// Add in style property hooks for overriding the default\n\t// behavior of getting and setting a style property\n\tcssHooks: {\n\t\topacity: {\n\t\t\tget: function( elem, computed ) {\n\t\t\t\tif ( computed ) {\n\n\t\t\t\t\t// We should always get a number back from opacity\n\t\t\t\t\tvar ret = curCSS( elem, \"opacity\" );\n\t\t\t\t\treturn ret === \"\" ? \"1\" : ret;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\t// Don't automatically add \"px\" to these possibly-unitless properties\n\tcssNumber: {\n\t\t\"columnCount\": true,\n\t\t\"fillOpacity\": true,\n\t\t\"flexGrow\": true,\n\t\t\"flexShrink\": true,\n\t\t\"fontWeight\": true,\n\t\t\"lineHeight\": true,\n\t\t\"opacity\": true,\n\t\t\"order\": true,\n\t\t\"orphans\": true,\n\t\t\"widows\": true,\n\t\t\"zIndex\": true,\n\t\t\"zoom\": true\n\t},\n\n\t// Add in properties whose names you wish to fix before\n\t// setting or getting the value\n\tcssProps: {\n\t\t\"float\": \"cssFloat\"\n\t},\n\n\t// Get and set the style property on a DOM Node\n\tstyle: function( elem, name, value, extra ) {\n\n\t\t// Don't set styles on text and comment nodes\n\t\tif ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Make sure that we're working with the right name\n\t\tvar ret, type, hooks,\n\t\t\torigName = jQuery.camelCase( name ),\n\t\t\tstyle = elem.style;\n\n\t\tname = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );\n\n\t\t// Gets hook for the prefixed version, then unprefixed version\n\t\thooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];\n\n\t\t// Check if we're setting a value\n\t\tif ( value !== undefined ) {\n\t\t\ttype = typeof value;\n\n\t\t\t// Convert \"+=\" or \"-=\" to relative numbers (#7345)\n\t\t\tif ( type === \"string\" && (ret = rrelNum.exec( value )) ) {\n\t\t\t\tvalue = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );\n\t\t\t\t// Fixes bug #9237\n\t\t\t\ttype = \"number\";\n\t\t\t}\n\n\t\t\t// Make sure that null and NaN values aren't set (#7116)\n\t\t\tif ( value == null || value !== value ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// If a number, add 'px' to the (except for certain CSS properties)\n\t\t\tif ( type === \"number\" && !jQuery.cssNumber[ origName ] ) {\n\t\t\t\tvalue += \"px\";\n\t\t\t}\n\n\t\t\t// Support: IE9-11+\n\t\t\t// background-* props affect original clone's values\n\t\t\tif ( !support.clearCloneStyle && value === \"\" && name.indexOf( \"background\" ) === 0 ) {\n\t\t\t\tstyle[ name ] = \"inherit\";\n\t\t\t}\n\n\t\t\t// If a hook was provided, use that value, otherwise just set the specified value\n\t\t\tif ( !hooks || !(\"set\" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {\n\t\t\t\tstyle[ name ] = value;\n\t\t\t}\n\n\t\t} else {\n\t\t\t// If a hook was provided get the non-computed value from there\n\t\t\tif ( hooks && \"get\" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {\n\t\t\t\treturn ret;\n\t\t\t}\n\n\t\t\t// Otherwise just get the value from the style object\n\t\t\treturn style[ name ];\n\t\t}\n\t},\n\n\tcss: function( elem, name, extra, styles ) {\n\t\tvar val, num, hooks,\n\t\t\torigName = jQuery.camelCase( name );\n\n\t\t// Make sure that we're working with the right name\n\t\tname = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );\n\n\t\t// Try prefixed name followed by the unprefixed name\n\t\thooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];\n\n\t\t// If a hook was provided get the computed value from there\n\t\tif ( hooks && \"get\" in hooks ) {\n\t\t\tval = hooks.get( elem, true, extra );\n\t\t}\n\n\t\t// Otherwise, if a way to get the computed value exists, use that\n\t\tif ( val === undefined ) {\n\t\t\tval = curCSS( elem, name, styles );\n\t\t}\n\n\t\t// Convert \"normal\" to computed value\n\t\tif ( val === \"normal\" && name in cssNormalTransform ) {\n\t\t\tval = cssNormalTransform[ name ];\n\t\t}\n\n\t\t// Make numeric if forced or a qualifier was provided and val looks numeric\n\t\tif ( extra === \"\" || extra ) {\n\t\t\tnum = parseFloat( val );\n\t\t\treturn extra === true || jQuery.isNumeric( num ) ? num || 0 : val;\n\t\t}\n\t\treturn val;\n\t}\n});\n\njQuery.each([ \"height\", \"width\" ], function( i, name ) {\n\tjQuery.cssHooks[ name ] = {\n\t\tget: function( elem, computed, extra ) {\n\t\t\tif ( computed ) {\n\n\t\t\t\t// Certain elements can have dimension info if we invisibly show them\n\t\t\t\t// but it must have a current display style that would benefit\n\t\t\t\treturn rdisplayswap.test( jQuery.css( elem, \"display\" ) ) && elem.offsetWidth === 0 ?\n\t\t\t\t\tjQuery.swap( elem, cssShow, function() {\n\t\t\t\t\t\treturn getWidthOrHeight( elem, name, extra );\n\t\t\t\t\t}) :\n\t\t\t\t\tgetWidthOrHeight( elem, name, extra );\n\t\t\t}\n\t\t},\n\n\t\tset: function( elem, value, extra ) {\n\t\t\tvar styles = extra && getStyles( elem );\n\t\t\treturn setPositiveNumber( elem, value, extra ?\n\t\t\t\taugmentWidthOrHeight(\n\t\t\t\t\telem,\n\t\t\t\t\tname,\n\t\t\t\t\textra,\n\t\t\t\t\tjQuery.css( elem, \"boxSizing\", false, styles ) === \"border-box\",\n\t\t\t\t\tstyles\n\t\t\t\t) : 0\n\t\t\t);\n\t\t}\n\t};\n});\n\n// Support: Android 2.3\njQuery.cssHooks.marginRight = addGetHookIf( support.reliableMarginRight,\n\tfunction( elem, computed ) {\n\t\tif ( computed ) {\n\t\t\treturn jQuery.swap( elem, { \"display\": \"inline-block\" },\n\t\t\t\tcurCSS, [ elem, \"marginRight\" ] );\n\t\t}\n\t}\n);\n\n// These hooks are used by animate to expand properties\njQuery.each({\n\tmargin: \"\",\n\tpadding: \"\",\n\tborder: \"Width\"\n}, function( prefix, suffix ) {\n\tjQuery.cssHooks[ prefix + suffix ] = {\n\t\texpand: function( value ) {\n\t\t\tvar i = 0,\n\t\t\t\texpanded = {},\n\n\t\t\t\t// Assumes a single number if not a string\n\t\t\t\tparts = typeof value === \"string\" ? value.split(\" \") : [ value ];\n\n\t\t\tfor ( ; i < 4; i++ ) {\n\t\t\t\texpanded[ prefix + cssExpand[ i ] + suffix ] =\n\t\t\t\t\tparts[ i ] || parts[ i - 2 ] || parts[ 0 ];\n\t\t\t}\n\n\t\t\treturn expanded;\n\t\t}\n\t};\n\n\tif ( !rmargin.test( prefix ) ) {\n\t\tjQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;\n\t}\n});\n\njQuery.fn.extend({\n\tcss: function( name, value ) {\n\t\treturn access( this, function( elem, name, value ) {\n\t\t\tvar styles, len,\n\t\t\t\tmap = {},\n\t\t\t\ti = 0;\n\n\t\t\tif ( jQuery.isArray( name ) ) {\n\t\t\t\tstyles = getStyles( elem );\n\t\t\t\tlen = name.length;\n\n\t\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\t\tmap[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );\n\t\t\t\t}\n\n\t\t\t\treturn map;\n\t\t\t}\n\n\t\t\treturn value !== undefined ?\n\t\t\t\tjQuery.style( elem, name, value ) :\n\t\t\t\tjQuery.css( elem, name );\n\t\t}, name, value, arguments.length > 1 );\n\t},\n\tshow: function() {\n\t\treturn showHide( this, true );\n\t},\n\thide: function() {\n\t\treturn showHide( this );\n\t},\n\ttoggle: function( state ) {\n\t\tif ( typeof state === \"boolean\" ) {\n\t\t\treturn state ? this.show() : this.hide();\n\t\t}\n\n\t\treturn this.each(function() {\n\t\t\tif ( isHidden( this ) ) {\n\t\t\t\tjQuery( this ).show();\n\t\t\t} else {\n\t\t\t\tjQuery( this ).hide();\n\t\t\t}\n\t\t});\n\t}\n});\n\n\nfunction Tween( elem, options, prop, end, easing ) {\n\treturn new Tween.prototype.init( elem, options, prop, end, easing );\n}\njQuery.Tween = Tween;\n\nTween.prototype = {\n\tconstructor: Tween,\n\tinit: function( elem, options, prop, end, easing, unit ) {\n\t\tthis.elem = elem;\n\t\tthis.prop = prop;\n\t\tthis.easing = easing || \"swing\";\n\t\tthis.options = options;\n\t\tthis.start = this.now = this.cur();\n\t\tthis.end = end;\n\t\tthis.unit = unit || ( jQuery.cssNumber[ prop ] ? \"\" : \"px\" );\n\t},\n\tcur: function() {\n\t\tvar hooks = Tween.propHooks[ this.prop ];\n\n\t\treturn hooks && hooks.get ?\n\t\t\thooks.get( this ) :\n\t\t\tTween.propHooks._default.get( this );\n\t},\n\trun: function( percent ) {\n\t\tvar eased,\n\t\t\thooks = Tween.propHooks[ this.prop ];\n\n\t\tif ( this.options.duration ) {\n\t\t\tthis.pos = eased = jQuery.easing[ this.easing ](\n\t\t\t\tpercent, this.options.duration * percent, 0, 1, this.options.duration\n\t\t\t);\n\t\t} else {\n\t\t\tthis.pos = eased = percent;\n\t\t}\n\t\tthis.now = ( this.end - this.start ) * eased + this.start;\n\n\t\tif ( this.options.step ) {\n\t\t\tthis.options.step.call( this.elem, this.now, this );\n\t\t}\n\n\t\tif ( hooks && hooks.set ) {\n\t\t\thooks.set( this );\n\t\t} else {\n\t\t\tTween.propHooks._default.set( this );\n\t\t}\n\t\treturn this;\n\t}\n};\n\nTween.prototype.init.prototype = Tween.prototype;\n\nTween.propHooks = {\n\t_default: {\n\t\tget: function( tween ) {\n\t\t\tvar result;\n\n\t\t\tif ( tween.elem[ tween.prop ] != null &&\n\t\t\t\t(!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {\n\t\t\t\treturn tween.elem[ tween.prop ];\n\t\t\t}\n\n\t\t\t// Passing an empty string as a 3rd parameter to .css will automatically\n\t\t\t// attempt a parseFloat and fallback to a string if the parse fails.\n\t\t\t// Simple values such as \"10px\" are parsed to Float;\n\t\t\t// complex values such as \"rotate(1rad)\" are returned as-is.\n\t\t\tresult = jQuery.css( tween.elem, tween.prop, \"\" );\n\t\t\t// Empty strings, null, undefined and \"auto\" are converted to 0.\n\t\t\treturn !result || result === \"auto\" ? 0 : result;\n\t\t},\n\t\tset: function( tween ) {\n\t\t\t// Use step hook for back compat.\n\t\t\t// Use cssHook if its there.\n\t\t\t// Use .style if available and use plain properties where available.\n\t\t\tif ( jQuery.fx.step[ tween.prop ] ) {\n\t\t\t\tjQuery.fx.step[ tween.prop ]( tween );\n\t\t\t} else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {\n\t\t\t\tjQuery.style( tween.elem, tween.prop, tween.now + tween.unit );\n\t\t\t} else {\n\t\t\t\ttween.elem[ tween.prop ] = tween.now;\n\t\t\t}\n\t\t}\n\t}\n};\n\n// Support: IE9\n// Panic based approach to setting things on disconnected nodes\nTween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {\n\tset: function( tween ) {\n\t\tif ( tween.elem.nodeType && tween.elem.parentNode ) {\n\t\t\ttween.elem[ tween.prop ] = tween.now;\n\t\t}\n\t}\n};\n\njQuery.easing = {\n\tlinear: function( p ) {\n\t\treturn p;\n\t},\n\tswing: function( p ) {\n\t\treturn 0.5 - Math.cos( p * Math.PI ) / 2;\n\t}\n};\n\njQuery.fx = Tween.prototype.init;\n\n// Back Compat <1.8 extension point\njQuery.fx.step = {};\n\n\n\n\nvar\n\tfxNow, timerId,\n\trfxtypes = /^(?:toggle|show|hide)$/,\n\trfxnum = new RegExp( \"^(?:([+-])=|)(\" + pnum + \")([a-z%]*)$\", \"i\" ),\n\trrun = /queueHooks$/,\n\tanimationPrefilters = [ defaultPrefilter ],\n\ttweeners = {\n\t\t\"*\": [ function( prop, value ) {\n\t\t\tvar tween = this.createTween( prop, value ),\n\t\t\t\ttarget = tween.cur(),\n\t\t\t\tparts = rfxnum.exec( value ),\n\t\t\t\tunit = parts && parts[ 3 ] || ( jQuery.cssNumber[ prop ] ? \"\" : \"px\" ),\n\n\t\t\t\t// Starting value computation is required for potential unit mismatches\n\t\t\t\tstart = ( jQuery.cssNumber[ prop ] || unit !== \"px\" && +target ) &&\n\t\t\t\t\trfxnum.exec( jQuery.css( tween.elem, prop ) ),\n\t\t\t\tscale = 1,\n\t\t\t\tmaxIterations = 20;\n\n\t\t\tif ( start && start[ 3 ] !== unit ) {\n\t\t\t\t// Trust units reported by jQuery.css\n\t\t\t\tunit = unit || start[ 3 ];\n\n\t\t\t\t// Make sure we update the tween properties later on\n\t\t\t\tparts = parts || [];\n\n\t\t\t\t// Iteratively approximate from a nonzero starting point\n\t\t\t\tstart = +target || 1;\n\n\t\t\t\tdo {\n\t\t\t\t\t// If previous iteration zeroed out, double until we get *something*.\n\t\t\t\t\t// Use string for doubling so we don't accidentally see scale as unchanged below\n\t\t\t\t\tscale = scale || \".5\";\n\n\t\t\t\t\t// Adjust and apply\n\t\t\t\t\tstart = start / scale;\n\t\t\t\t\tjQuery.style( tween.elem, prop, start + unit );\n\n\t\t\t\t// Update scale, tolerating zero or NaN from tween.cur(),\n\t\t\t\t// break the loop if scale is unchanged or perfect, or if we've just had enough\n\t\t\t\t} while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );\n\t\t\t}\n\n\t\t\t// Update tween properties\n\t\t\tif ( parts ) {\n\t\t\t\tstart = tween.start = +start || +target || 0;\n\t\t\t\ttween.unit = unit;\n\t\t\t\t// If a +=/-= token was provided, we're doing a relative animation\n\t\t\t\ttween.end = parts[ 1 ] ?\n\t\t\t\t\tstart + ( parts[ 1 ] + 1 ) * parts[ 2 ] :\n\t\t\t\t\t+parts[ 2 ];\n\t\t\t}\n\n\t\t\treturn tween;\n\t\t} ]\n\t};\n\n// Animations created synchronously will run synchronously\nfunction createFxNow() {\n\tsetTimeout(function() {\n\t\tfxNow = undefined;\n\t});\n\treturn ( fxNow = jQuery.now() );\n}\n\n// Generate parameters to create a standard animation\nfunction genFx( type, includeWidth ) {\n\tvar which,\n\t\ti = 0,\n\t\tattrs = { height: type };\n\n\t// If we include width, step value is 1 to do all cssExpand values,\n\t// otherwise step value is 2 to skip over Left and Right\n\tincludeWidth = includeWidth ? 1 : 0;\n\tfor ( ; i < 4 ; i += 2 - includeWidth ) {\n\t\twhich = cssExpand[ i ];\n\t\tattrs[ \"margin\" + which ] = attrs[ \"padding\" + which ] = type;\n\t}\n\n\tif ( includeWidth ) {\n\t\tattrs.opacity = attrs.width = type;\n\t}\n\n\treturn attrs;\n}\n\nfunction createTween( value, prop, animation ) {\n\tvar tween,\n\t\tcollection = ( tweeners[ prop ] || [] ).concat( tweeners[ \"*\" ] ),\n\t\tindex = 0,\n\t\tlength = collection.length;\n\tfor ( ; index < length; index++ ) {\n\t\tif ( (tween = collection[ index ].call( animation, prop, value )) ) {\n\n\t\t\t// We're done with this property\n\t\t\treturn tween;\n\t\t}\n\t}\n}\n\nfunction defaultPrefilter( elem, props, opts ) {\n\t/* jshint validthis: true */\n\tvar prop, value, toggle, tween, hooks, oldfire, display, checkDisplay,\n\t\tanim = this,\n\t\torig = {},\n\t\tstyle = elem.style,\n\t\thidden = elem.nodeType && isHidden( elem ),\n\t\tdataShow = data_priv.get( elem, \"fxshow\" );\n\n\t// Handle queue: false promises\n\tif ( !opts.queue ) {\n\t\thooks = jQuery._queueHooks( elem, \"fx\" );\n\t\tif ( hooks.unqueued == null ) {\n\t\t\thooks.unqueued = 0;\n\t\t\toldfire = hooks.empty.fire;\n\t\t\thooks.empty.fire = function() {\n\t\t\t\tif ( !hooks.unqueued ) {\n\t\t\t\t\toldfire();\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t\thooks.unqueued++;\n\n\t\tanim.always(function() {\n\t\t\t// Ensure the complete handler is called before this completes\n\t\t\tanim.always(function() {\n\t\t\t\thooks.unqueued--;\n\t\t\t\tif ( !jQuery.queue( elem, \"fx\" ).length ) {\n\t\t\t\t\thooks.empty.fire();\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\t// Height/width overflow pass\n\tif ( elem.nodeType === 1 && ( \"height\" in props || \"width\" in props ) ) {\n\t\t// Make sure that nothing sneaks out\n\t\t// Record all 3 overflow attributes because IE9-10 do not\n\t\t// change the overflow attribute when overflowX and\n\t\t// overflowY are set to the same value\n\t\topts.overflow = [ style.overflow, style.overflowX, style.overflowY ];\n\n\t\t// Set display property to inline-block for height/width\n\t\t// animations on inline elements that are having width/height animated\n\t\tdisplay = jQuery.css( elem, \"display\" );\n\n\t\t// Test default display if display is currently \"none\"\n\t\tcheckDisplay = display === \"none\" ?\n\t\t\tdata_priv.get( elem, \"olddisplay\" ) || defaultDisplay( elem.nodeName ) : display;\n\n\t\tif ( checkDisplay === \"inline\" && jQuery.css( elem, \"float\" ) === \"none\" ) {\n\t\t\tstyle.display = \"inline-block\";\n\t\t}\n\t}\n\n\tif ( opts.overflow ) {\n\t\tstyle.overflow = \"hidden\";\n\t\tanim.always(function() {\n\t\t\tstyle.overflow = opts.overflow[ 0 ];\n\t\t\tstyle.overflowX = opts.overflow[ 1 ];\n\t\t\tstyle.overflowY = opts.overflow[ 2 ];\n\t\t});\n\t}\n\n\t// show/hide pass\n\tfor ( prop in props ) {\n\t\tvalue = props[ prop ];\n\t\tif ( rfxtypes.exec( value ) ) {\n\t\t\tdelete props[ prop ];\n\t\t\ttoggle = toggle || value === \"toggle\";\n\t\t\tif ( value === ( hidden ? \"hide\" : \"show\" ) ) {\n\n\t\t\t\t// If there is dataShow left over from a stopped hide or show and we are going to proceed with show, we should pretend to be hidden\n\t\t\t\tif ( value === \"show\" && dataShow && dataShow[ prop ] !== undefined ) {\n\t\t\t\t\thidden = true;\n\t\t\t\t} else {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\torig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );\n\n\t\t// Any non-fx value stops us from restoring the original display value\n\t\t} else {\n\t\t\tdisplay = undefined;\n\t\t}\n\t}\n\n\tif ( !jQuery.isEmptyObject( orig ) ) {\n\t\tif ( dataShow ) {\n\t\t\tif ( \"hidden\" in dataShow ) {\n\t\t\t\thidden = dataShow.hidden;\n\t\t\t}\n\t\t} else {\n\t\t\tdataShow = data_priv.access( elem, \"fxshow\", {} );\n\t\t}\n\n\t\t// Store state if its toggle - enables .stop().toggle() to \"reverse\"\n\t\tif ( toggle ) {\n\t\t\tdataShow.hidden = !hidden;\n\t\t}\n\t\tif ( hidden ) {\n\t\t\tjQuery( elem ).show();\n\t\t} else {\n\t\t\tanim.done(function() {\n\t\t\t\tjQuery( elem ).hide();\n\t\t\t});\n\t\t}\n\t\tanim.done(function() {\n\t\t\tvar prop;\n\n\t\t\tdata_priv.remove( elem, \"fxshow\" );\n\t\t\tfor ( prop in orig ) {\n\t\t\t\tjQuery.style( elem, prop, orig[ prop ] );\n\t\t\t}\n\t\t});\n\t\tfor ( prop in orig ) {\n\t\t\ttween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );\n\n\t\t\tif ( !( prop in dataShow ) ) {\n\t\t\t\tdataShow[ prop ] = tween.start;\n\t\t\t\tif ( hidden ) {\n\t\t\t\t\ttween.end = tween.start;\n\t\t\t\t\ttween.start = prop === \"width\" || prop === \"height\" ? 1 : 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t// If this is a noop like .hide().hide(), restore an overwritten display value\n\t} else if ( (display === \"none\" ? defaultDisplay( elem.nodeName ) : display) === \"inline\" ) {\n\t\tstyle.display = display;\n\t}\n}\n\nfunction propFilter( props, specialEasing ) {\n\tvar index, name, easing, value, hooks;\n\n\t// camelCase, specialEasing and expand cssHook pass\n\tfor ( index in props ) {\n\t\tname = jQuery.camelCase( index );\n\t\teasing = specialEasing[ name ];\n\t\tvalue = props[ index ];\n\t\tif ( jQuery.isArray( value ) ) {\n\t\t\teasing = value[ 1 ];\n\t\t\tvalue = props[ index ] = value[ 0 ];\n\t\t}\n\n\t\tif ( index !== name ) {\n\t\t\tprops[ name ] = value;\n\t\t\tdelete props[ index ];\n\t\t}\n\n\t\thooks = jQuery.cssHooks[ name ];\n\t\tif ( hooks && \"expand\" in hooks ) {\n\t\t\tvalue = hooks.expand( value );\n\t\t\tdelete props[ name ];\n\n\t\t\t// Not quite $.extend, this won't overwrite existing keys.\n\t\t\t// Reusing 'index' because we have the correct \"name\"\n\t\t\tfor ( index in value ) {\n\t\t\t\tif ( !( index in props ) ) {\n\t\t\t\t\tprops[ index ] = value[ index ];\n\t\t\t\t\tspecialEasing[ index ] = easing;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tspecialEasing[ name ] = easing;\n\t\t}\n\t}\n}\n\nfunction Animation( elem, properties, options ) {\n\tvar result,\n\t\tstopped,\n\t\tindex = 0,\n\t\tlength = animationPrefilters.length,\n\t\tdeferred = jQuery.Deferred().always( function() {\n\t\t\t// Don't match elem in the :animated selector\n\t\t\tdelete tick.elem;\n\t\t}),\n\t\ttick = function() {\n\t\t\tif ( stopped ) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tvar currentTime = fxNow || createFxNow(),\n\t\t\t\tremaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),\n\t\t\t\t// Support: Android 2.3\n\t\t\t\t// Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497)\n\t\t\t\ttemp = remaining / animation.duration || 0,\n\t\t\t\tpercent = 1 - temp,\n\t\t\t\tindex = 0,\n\t\t\t\tlength = animation.tweens.length;\n\n\t\t\tfor ( ; index < length ; index++ ) {\n\t\t\t\tanimation.tweens[ index ].run( percent );\n\t\t\t}\n\n\t\t\tdeferred.notifyWith( elem, [ animation, percent, remaining ]);\n\n\t\t\tif ( percent < 1 && length ) {\n\t\t\t\treturn remaining;\n\t\t\t} else {\n\t\t\t\tdeferred.resolveWith( elem, [ animation ] );\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\t\tanimation = deferred.promise({\n\t\t\telem: elem,\n\t\t\tprops: jQuery.extend( {}, properties ),\n\t\t\topts: jQuery.extend( true, { specialEasing: {} }, options ),\n\t\t\toriginalProperties: properties,\n\t\t\toriginalOptions: options,\n\t\t\tstartTime: fxNow || createFxNow(),\n\t\t\tduration: options.duration,\n\t\t\ttweens: [],\n\t\t\tcreateTween: function( prop, end ) {\n\t\t\t\tvar tween = jQuery.Tween( elem, animation.opts, prop, end,\n\t\t\t\t\t\tanimation.opts.specialEasing[ prop ] || animation.opts.easing );\n\t\t\t\tanimation.tweens.push( tween );\n\t\t\t\treturn tween;\n\t\t\t},\n\t\t\tstop: function( gotoEnd ) {\n\t\t\t\tvar index = 0,\n\t\t\t\t\t// If we are going to the end, we want to run all the tweens\n\t\t\t\t\t// otherwise we skip this part\n\t\t\t\t\tlength = gotoEnd ? animation.tweens.length : 0;\n\t\t\t\tif ( stopped ) {\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t\tstopped = true;\n\t\t\t\tfor ( ; index < length ; index++ ) {\n\t\t\t\t\tanimation.tweens[ index ].run( 1 );\n\t\t\t\t}\n\n\t\t\t\t// Resolve when we played the last frame; otherwise, reject\n\t\t\t\tif ( gotoEnd ) {\n\t\t\t\t\tdeferred.resolveWith( elem, [ animation, gotoEnd ] );\n\t\t\t\t} else {\n\t\t\t\t\tdeferred.rejectWith( elem, [ animation, gotoEnd ] );\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t}\n\t\t}),\n\t\tprops = animation.props;\n\n\tpropFilter( props, animation.opts.specialEasing );\n\n\tfor ( ; index < length ; index++ ) {\n\t\tresult = animationPrefilters[ index ].call( animation, elem, props, animation.opts );\n\t\tif ( result ) {\n\t\t\treturn result;\n\t\t}\n\t}\n\n\tjQuery.map( props, createTween, animation );\n\n\tif ( jQuery.isFunction( animation.opts.start ) ) {\n\t\tanimation.opts.start.call( elem, animation );\n\t}\n\n\tjQuery.fx.timer(\n\t\tjQuery.extend( tick, {\n\t\t\telem: elem,\n\t\t\tanim: animation,\n\t\t\tqueue: animation.opts.queue\n\t\t})\n\t);\n\n\t// attach callbacks from options\n\treturn animation.progress( animation.opts.progress )\n\t\t.done( animation.opts.done, animation.opts.complete )\n\t\t.fail( animation.opts.fail )\n\t\t.always( animation.opts.always );\n}\n\njQuery.Animation = jQuery.extend( Animation, {\n\n\ttweener: function( props, callback ) {\n\t\tif ( jQuery.isFunction( props ) ) {\n\t\t\tcallback = props;\n\t\t\tprops = [ \"*\" ];\n\t\t} else {\n\t\t\tprops = props.split(\" \");\n\t\t}\n\n\t\tvar prop,\n\t\t\tindex = 0,\n\t\t\tlength = props.length;\n\n\t\tfor ( ; index < length ; index++ ) {\n\t\t\tprop = props[ index ];\n\t\t\ttweeners[ prop ] = tweeners[ prop ] || [];\n\t\t\ttweeners[ prop ].unshift( callback );\n\t\t}\n\t},\n\n\tprefilter: function( callback, prepend ) {\n\t\tif ( prepend ) {\n\t\t\tanimationPrefilters.unshift( callback );\n\t\t} else {\n\t\t\tanimationPrefilters.push( callback );\n\t\t}\n\t}\n});\n\njQuery.speed = function( speed, easing, fn ) {\n\tvar opt = speed && typeof speed === \"object\" ? jQuery.extend( {}, speed ) : {\n\t\tcomplete: fn || !fn && easing ||\n\t\t\tjQuery.isFunction( speed ) && speed,\n\t\tduration: speed,\n\t\teasing: fn && easing || easing && !jQuery.isFunction( easing ) && easing\n\t};\n\n\topt.duration = jQuery.fx.off ? 0 : typeof opt.duration === \"number\" ? opt.duration :\n\t\topt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;\n\n\t// Normalize opt.queue - true/undefined/null -> \"fx\"\n\tif ( opt.queue == null || opt.queue === true ) {\n\t\topt.queue = \"fx\";\n\t}\n\n\t// Queueing\n\topt.old = opt.complete;\n\n\topt.complete = function() {\n\t\tif ( jQuery.isFunction( opt.old ) ) {\n\t\t\topt.old.call( this );\n\t\t}\n\n\t\tif ( opt.queue ) {\n\t\t\tjQuery.dequeue( this, opt.queue );\n\t\t}\n\t};\n\n\treturn opt;\n};\n\njQuery.fn.extend({\n\tfadeTo: function( speed, to, easing, callback ) {\n\n\t\t// Show any hidden elements after setting opacity to 0\n\t\treturn this.filter( isHidden ).css( \"opacity\", 0 ).show()\n\n\t\t\t// Animate to the value specified\n\t\t\t.end().animate({ opacity: to }, speed, easing, callback );\n\t},\n\tanimate: function( prop, speed, easing, callback ) {\n\t\tvar empty = jQuery.isEmptyObject( prop ),\n\t\t\toptall = jQuery.speed( speed, easing, callback ),\n\t\t\tdoAnimation = function() {\n\t\t\t\t// Operate on a copy of prop so per-property easing won't be lost\n\t\t\t\tvar anim = Animation( this, jQuery.extend( {}, prop ), optall );\n\n\t\t\t\t// Empty animations, or finishing resolves immediately\n\t\t\t\tif ( empty || data_priv.get( this, \"finish\" ) ) {\n\t\t\t\t\tanim.stop( true );\n\t\t\t\t}\n\t\t\t};\n\t\t\tdoAnimation.finish = doAnimation;\n\n\t\treturn empty || optall.queue === false ?\n\t\t\tthis.each( doAnimation ) :\n\t\t\tthis.queue( optall.queue, doAnimation );\n\t},\n\tstop: function( type, clearQueue, gotoEnd ) {\n\t\tvar stopQueue = function( hooks ) {\n\t\t\tvar stop = hooks.stop;\n\t\t\tdelete hooks.stop;\n\t\t\tstop( gotoEnd );\n\t\t};\n\n\t\tif ( typeof type !== \"string\" ) {\n\t\t\tgotoEnd = clearQueue;\n\t\t\tclearQueue = type;\n\t\t\ttype = undefined;\n\t\t}\n\t\tif ( clearQueue && type !== false ) {\n\t\t\tthis.queue( type || \"fx\", [] );\n\t\t}\n\n\t\treturn this.each(function() {\n\t\t\tvar dequeue = true,\n\t\t\t\tindex = type != null && type + \"queueHooks\",\n\t\t\t\ttimers = jQuery.timers,\n\t\t\t\tdata = data_priv.get( this );\n\n\t\t\tif ( index ) {\n\t\t\t\tif ( data[ index ] && data[ index ].stop ) {\n\t\t\t\t\tstopQueue( data[ index ] );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor ( index in data ) {\n\t\t\t\t\tif ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {\n\t\t\t\t\t\tstopQueue( data[ index ] );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor ( index = timers.length; index--; ) {\n\t\t\t\tif ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {\n\t\t\t\t\ttimers[ index ].anim.stop( gotoEnd );\n\t\t\t\t\tdequeue = false;\n\t\t\t\t\ttimers.splice( index, 1 );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Start the next in the queue if the last step wasn't forced.\n\t\t\t// Timers currently will call their complete callbacks, which\n\t\t\t// will dequeue but only if they were gotoEnd.\n\t\t\tif ( dequeue || !gotoEnd ) {\n\t\t\t\tjQuery.dequeue( this, type );\n\t\t\t}\n\t\t});\n\t},\n\tfinish: function( type ) {\n\t\tif ( type !== false ) {\n\t\t\ttype = type || \"fx\";\n\t\t}\n\t\treturn this.each(function() {\n\t\t\tvar index,\n\t\t\t\tdata = data_priv.get( this ),\n\t\t\t\tqueue = data[ type + \"queue\" ],\n\t\t\t\thooks = data[ type + \"queueHooks\" ],\n\t\t\t\ttimers = jQuery.timers,\n\t\t\t\tlength = queue ? queue.length : 0;\n\n\t\t\t// Enable finishing flag on private data\n\t\t\tdata.finish = true;\n\n\t\t\t// Empty the queue first\n\t\t\tjQuery.queue( this, type, [] );\n\n\t\t\tif ( hooks && hooks.stop ) {\n\t\t\t\thooks.stop.call( this, true );\n\t\t\t}\n\n\t\t\t// Look for any active animations, and finish them\n\t\t\tfor ( index = timers.length; index--; ) {\n\t\t\t\tif ( timers[ index ].elem === this && timers[ index ].queue === type ) {\n\t\t\t\t\ttimers[ index ].anim.stop( true );\n\t\t\t\t\ttimers.splice( index, 1 );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Look for any animations in the old queue and finish them\n\t\t\tfor ( index = 0; index < length; index++ ) {\n\t\t\t\tif ( queue[ index ] && queue[ index ].finish ) {\n\t\t\t\t\tqueue[ index ].finish.call( this );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Turn off finishing flag\n\t\t\tdelete data.finish;\n\t\t});\n\t}\n});\n\njQuery.each([ \"toggle\", \"show\", \"hide\" ], function( i, name ) {\n\tvar cssFn = jQuery.fn[ name ];\n\tjQuery.fn[ name ] = function( speed, easing, callback ) {\n\t\treturn speed == null || typeof speed === \"boolean\" ?\n\t\t\tcssFn.apply( this, arguments ) :\n\t\t\tthis.animate( genFx( name, true ), speed, easing, callback );\n\t};\n});\n\n// Generate shortcuts for custom animations\njQuery.each({\n\tslideDown: genFx(\"show\"),\n\tslideUp: genFx(\"hide\"),\n\tslideToggle: genFx(\"toggle\"),\n\tfadeIn: { opacity: \"show\" },\n\tfadeOut: { opacity: \"hide\" },\n\tfadeToggle: { opacity: \"toggle\" }\n}, function( name, props ) {\n\tjQuery.fn[ name ] = function( speed, easing, callback ) {\n\t\treturn this.animate( props, speed, easing, callback );\n\t};\n});\n\njQuery.timers = [];\njQuery.fx.tick = function() {\n\tvar timer,\n\t\ti = 0,\n\t\ttimers = jQuery.timers;\n\n\tfxNow = jQuery.now();\n\n\tfor ( ; i < timers.length; i++ ) {\n\t\ttimer = timers[ i ];\n\t\t// Checks the timer has not already been removed\n\t\tif ( !timer() && timers[ i ] === timer ) {\n\t\t\ttimers.splice( i--, 1 );\n\t\t}\n\t}\n\n\tif ( !timers.length ) {\n\t\tjQuery.fx.stop();\n\t}\n\tfxNow = undefined;\n};\n\njQuery.fx.timer = function( timer ) {\n\tjQuery.timers.push( timer );\n\tif ( timer() ) {\n\t\tjQuery.fx.start();\n\t} else {\n\t\tjQuery.timers.pop();\n\t}\n};\n\njQuery.fx.interval = 13;\n\njQuery.fx.start = function() {\n\tif ( !timerId ) {\n\t\ttimerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );\n\t}\n};\n\njQuery.fx.stop = function() {\n\tclearInterval( timerId );\n\ttimerId = null;\n};\n\njQuery.fx.speeds = {\n\tslow: 600,\n\tfast: 200,\n\t// Default speed\n\t_default: 400\n};\n\n\n// Based off of the plugin by Clint Helfers, with permission.\n// http://blindsignals.com/index.php/2009/07/jquery-delay/\njQuery.fn.delay = function( time, type ) {\n\ttime = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;\n\ttype = type || \"fx\";\n\n\treturn this.queue( type, function( next, hooks ) {\n\t\tvar timeout = setTimeout( next, time );\n\t\thooks.stop = function() {\n\t\t\tclearTimeout( timeout );\n\t\t};\n\t});\n};\n\n\n(function() {\n\tvar input = document.createElement( \"input\" ),\n\t\tselect = document.createElement( \"select\" ),\n\t\topt = select.appendChild( document.createElement( \"option\" ) );\n\n\tinput.type = \"checkbox\";\n\n\t// Support: iOS<=5.1, Android<=4.2+\n\t// Default value for a checkbox should be \"on\"\n\tsupport.checkOn = input.value !== \"\";\n\n\t// Support: IE<=11+\n\t// Must access selectedIndex to make default options select\n\tsupport.optSelected = opt.selected;\n\n\t// Support: Android<=2.3\n\t// Options inside disabled selects are incorrectly marked as disabled\n\tselect.disabled = true;\n\tsupport.optDisabled = !opt.disabled;\n\n\t// Support: IE<=11+\n\t// An input loses its value after becoming a radio\n\tinput = document.createElement( \"input\" );\n\tinput.value = \"t\";\n\tinput.type = \"radio\";\n\tsupport.radioValue = input.value === \"t\";\n})();\n\n\nvar nodeHook, boolHook,\n\tattrHandle = jQuery.expr.attrHandle;\n\njQuery.fn.extend({\n\tattr: function( name, value ) {\n\t\treturn access( this, jQuery.attr, name, value, arguments.length > 1 );\n\t},\n\n\tremoveAttr: function( name ) {\n\t\treturn this.each(function() {\n\t\t\tjQuery.removeAttr( this, name );\n\t\t});\n\t}\n});\n\njQuery.extend({\n\tattr: function( elem, name, value ) {\n\t\tvar hooks, ret,\n\t\t\tnType = elem.nodeType;\n\n\t\t// don't get/set attributes on text, comment and attribute nodes\n\t\tif ( !elem || nType === 3 || nType === 8 || nType === 2 ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Fallback to prop when attributes are not supported\n\t\tif ( typeof elem.getAttribute === strundefined ) {\n\t\t\treturn jQuery.prop( elem, name, value );\n\t\t}\n\n\t\t// All attributes are lowercase\n\t\t// Grab necessary hook if one is defined\n\t\tif ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {\n\t\t\tname = name.toLowerCase();\n\t\t\thooks = jQuery.attrHooks[ name ] ||\n\t\t\t\t( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );\n\t\t}\n\n\t\tif ( value !== undefined ) {\n\n\t\t\tif ( value === null ) {\n\t\t\t\tjQuery.removeAttr( elem, name );\n\n\t\t\t} else if ( hooks && \"set\" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {\n\t\t\t\treturn ret;\n\n\t\t\t} else {\n\t\t\t\telem.setAttribute( name, value + \"\" );\n\t\t\t\treturn value;\n\t\t\t}\n\n\t\t} else if ( hooks && \"get\" in hooks && (ret = hooks.get( elem, name )) !== null ) {\n\t\t\treturn ret;\n\n\t\t} else {\n\t\t\tret = jQuery.find.attr( elem, name );\n\n\t\t\t// Non-existent attributes return null, we normalize to undefined\n\t\t\treturn ret == null ?\n\t\t\t\tundefined :\n\t\t\t\tret;\n\t\t}\n\t},\n\n\tremoveAttr: function( elem, value ) {\n\t\tvar name, propName,\n\t\t\ti = 0,\n\t\t\tattrNames = value && value.match( rnotwhite );\n\n\t\tif ( attrNames && elem.nodeType === 1 ) {\n\t\t\twhile ( (name = attrNames[i++]) ) {\n\t\t\t\tpropName = jQuery.propFix[ name ] || name;\n\n\t\t\t\t// Boolean attributes get special treatment (#10870)\n\t\t\t\tif ( jQuery.expr.match.bool.test( name ) ) {\n\t\t\t\t\t// Set corresponding property to false\n\t\t\t\t\telem[ propName ] = false;\n\t\t\t\t}\n\n\t\t\t\telem.removeAttribute( name );\n\t\t\t}\n\t\t}\n\t},\n\n\tattrHooks: {\n\t\ttype: {\n\t\t\tset: function( elem, value ) {\n\t\t\t\tif ( !support.radioValue && value === \"radio\" &&\n\t\t\t\t\tjQuery.nodeName( elem, \"input\" ) ) {\n\t\t\t\t\tvar val = elem.value;\n\t\t\t\t\telem.setAttribute( \"type\", value );\n\t\t\t\t\tif ( val ) {\n\t\t\t\t\t\telem.value = val;\n\t\t\t\t\t}\n\t\t\t\t\treturn value;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n});\n\n// Hooks for boolean attributes\nboolHook = {\n\tset: function( elem, value, name ) {\n\t\tif ( value === false ) {\n\t\t\t// Remove boolean attributes when set to false\n\t\t\tjQuery.removeAttr( elem, name );\n\t\t} else {\n\t\t\telem.setAttribute( name, name );\n\t\t}\n\t\treturn name;\n\t}\n};\njQuery.each( jQuery.expr.match.bool.source.match( /\\w+/g ), function( i, name ) {\n\tvar getter = attrHandle[ name ] || jQuery.find.attr;\n\n\tattrHandle[ name ] = function( elem, name, isXML ) {\n\t\tvar ret, handle;\n\t\tif ( !isXML ) {\n\t\t\t// Avoid an infinite loop by temporarily removing this function from the getter\n\t\t\thandle = attrHandle[ name ];\n\t\t\tattrHandle[ name ] = ret;\n\t\t\tret = getter( elem, name, isXML ) != null ?\n\t\t\t\tname.toLowerCase() :\n\t\t\t\tnull;\n\t\t\tattrHandle[ name ] = handle;\n\t\t}\n\t\treturn ret;\n\t};\n});\n\n\n\n\nvar rfocusable = /^(?:input|select|textarea|button)$/i;\n\njQuery.fn.extend({\n\tprop: function( name, value ) {\n\t\treturn access( this, jQuery.prop, name, value, arguments.length > 1 );\n\t},\n\n\tremoveProp: function( name ) {\n\t\treturn this.each(function() {\n\t\t\tdelete this[ jQuery.propFix[ name ] || name ];\n\t\t});\n\t}\n});\n\njQuery.extend({\n\tpropFix: {\n\t\t\"for\": \"htmlFor\",\n\t\t\"class\": \"className\"\n\t},\n\n\tprop: function( elem, name, value ) {\n\t\tvar ret, hooks, notxml,\n\t\t\tnType = elem.nodeType;\n\n\t\t// Don't get/set properties on text, comment and attribute nodes\n\t\tif ( !elem || nType === 3 || nType === 8 || nType === 2 ) {\n\t\t\treturn;\n\t\t}\n\n\t\tnotxml = nType !== 1 || !jQuery.isXMLDoc( elem );\n\n\t\tif ( notxml ) {\n\t\t\t// Fix name and attach hooks\n\t\t\tname = jQuery.propFix[ name ] || name;\n\t\t\thooks = jQuery.propHooks[ name ];\n\t\t}\n\n\t\tif ( value !== undefined ) {\n\t\t\treturn hooks && \"set\" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ?\n\t\t\t\tret :\n\t\t\t\t( elem[ name ] = value );\n\n\t\t} else {\n\t\t\treturn hooks && \"get\" in hooks && (ret = hooks.get( elem, name )) !== null ?\n\t\t\t\tret :\n\t\t\t\telem[ name ];\n\t\t}\n\t},\n\n\tpropHooks: {\n\t\ttabIndex: {\n\t\t\tget: function( elem ) {\n\t\t\t\treturn elem.hasAttribute( \"tabindex\" ) || rfocusable.test( elem.nodeName ) || elem.href ?\n\t\t\t\t\telem.tabIndex :\n\t\t\t\t\t-1;\n\t\t\t}\n\t\t}\n\t}\n});\n\nif ( !support.optSelected ) {\n\tjQuery.propHooks.selected = {\n\t\tget: function( elem ) {\n\t\t\tvar parent = elem.parentNode;\n\t\t\tif ( parent && parent.parentNode ) {\n\t\t\t\tparent.parentNode.selectedIndex;\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\t};\n}\n\njQuery.each([\n\t\"tabIndex\",\n\t\"readOnly\",\n\t\"maxLength\",\n\t\"cellSpacing\",\n\t\"cellPadding\",\n\t\"rowSpan\",\n\t\"colSpan\",\n\t\"useMap\",\n\t\"frameBorder\",\n\t\"contentEditable\"\n], function() {\n\tjQuery.propFix[ this.toLowerCase() ] = this;\n});\n\n\n\n\nvar rclass = /[\\t\\r\\n\\f]/g;\n\njQuery.fn.extend({\n\taddClass: function( value ) {\n\t\tvar classes, elem, cur, clazz, j, finalValue,\n\t\t\tproceed = typeof value === \"string\" && value,\n\t\t\ti = 0,\n\t\t\tlen = this.length;\n\n\t\tif ( jQuery.isFunction( value ) ) {\n\t\t\treturn this.each(function( j ) {\n\t\t\t\tjQuery( this ).addClass( value.call( this, j, this.className ) );\n\t\t\t});\n\t\t}\n\n\t\tif ( proceed ) {\n\t\t\t// The disjunction here is for better compressibility (see removeClass)\n\t\t\tclasses = ( value || \"\" ).match( rnotwhite ) || [];\n\n\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\telem = this[ i ];\n\t\t\t\tcur = elem.nodeType === 1 && ( elem.className ?\n\t\t\t\t\t( \" \" + elem.className + \" \" ).replace( rclass, \" \" ) :\n\t\t\t\t\t\" \"\n\t\t\t\t);\n\n\t\t\t\tif ( cur ) {\n\t\t\t\t\tj = 0;\n\t\t\t\t\twhile ( (clazz = classes[j++]) ) {\n\t\t\t\t\t\tif ( cur.indexOf( \" \" + clazz + \" \" ) < 0 ) {\n\t\t\t\t\t\t\tcur += clazz + \" \";\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// only assign if different to avoid unneeded rendering.\n\t\t\t\t\tfinalValue = jQuery.trim( cur );\n\t\t\t\t\tif ( elem.className !== finalValue ) {\n\t\t\t\t\t\telem.className = finalValue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tremoveClass: function( value ) {\n\t\tvar classes, elem, cur, clazz, j, finalValue,\n\t\t\tproceed = arguments.length === 0 || typeof value === \"string\" && value,\n\t\t\ti = 0,\n\t\t\tlen = this.length;\n\n\t\tif ( jQuery.isFunction( value ) ) {\n\t\t\treturn this.each(function( j ) {\n\t\t\t\tjQuery( this ).removeClass( value.call( this, j, this.className ) );\n\t\t\t});\n\t\t}\n\t\tif ( proceed ) {\n\t\t\tclasses = ( value || \"\" ).match( rnotwhite ) || [];\n\n\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\telem = this[ i ];\n\t\t\t\t// This expression is here for better compressibility (see addClass)\n\t\t\t\tcur = elem.nodeType === 1 && ( elem.className ?\n\t\t\t\t\t( \" \" + elem.className + \" \" ).replace( rclass, \" \" ) :\n\t\t\t\t\t\"\"\n\t\t\t\t);\n\n\t\t\t\tif ( cur ) {\n\t\t\t\t\tj = 0;\n\t\t\t\t\twhile ( (clazz = classes[j++]) ) {\n\t\t\t\t\t\t// Remove *all* instances\n\t\t\t\t\t\twhile ( cur.indexOf( \" \" + clazz + \" \" ) >= 0 ) {\n\t\t\t\t\t\t\tcur = cur.replace( \" \" + clazz + \" \", \" \" );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Only assign if different to avoid unneeded rendering.\n\t\t\t\t\tfinalValue = value ? jQuery.trim( cur ) : \"\";\n\t\t\t\t\tif ( elem.className !== finalValue ) {\n\t\t\t\t\t\telem.className = finalValue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\ttoggleClass: function( value, stateVal ) {\n\t\tvar type = typeof value;\n\n\t\tif ( typeof stateVal === \"boolean\" && type === \"string\" ) {\n\t\t\treturn stateVal ? this.addClass( value ) : this.removeClass( value );\n\t\t}\n\n\t\tif ( jQuery.isFunction( value ) ) {\n\t\t\treturn this.each(function( i ) {\n\t\t\t\tjQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );\n\t\t\t});\n\t\t}\n\n\t\treturn this.each(function() {\n\t\t\tif ( type === \"string\" ) {\n\t\t\t\t// Toggle individual class names\n\t\t\t\tvar className,\n\t\t\t\t\ti = 0,\n\t\t\t\t\tself = jQuery( this ),\n\t\t\t\t\tclassNames = value.match( rnotwhite ) || [];\n\n\t\t\t\twhile ( (className = classNames[ i++ ]) ) {\n\t\t\t\t\t// Check each className given, space separated list\n\t\t\t\t\tif ( self.hasClass( className ) ) {\n\t\t\t\t\t\tself.removeClass( className );\n\t\t\t\t\t} else {\n\t\t\t\t\t\tself.addClass( className );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t// Toggle whole class name\n\t\t\t} else if ( type === strundefined || type === \"boolean\" ) {\n\t\t\t\tif ( this.className ) {\n\t\t\t\t\t// store className if set\n\t\t\t\t\tdata_priv.set( this, \"__className__\", this.className );\n\t\t\t\t}\n\n\t\t\t\t// If the element has a class name or if we're passed `false`,\n\t\t\t\t// then remove the whole classname (if there was one, the above saved it).\n\t\t\t\t// Otherwise bring back whatever was previously saved (if anything),\n\t\t\t\t// falling back to the empty string if nothing was stored.\n\t\t\t\tthis.className = this.className || value === false ? \"\" : data_priv.get( this, \"__className__\" ) || \"\";\n\t\t\t}\n\t\t});\n\t},\n\n\thasClass: function( selector ) {\n\t\tvar className = \" \" + selector + \" \",\n\t\t\ti = 0,\n\t\t\tl = this.length;\n\t\tfor ( ; i < l; i++ ) {\n\t\t\tif ( this[i].nodeType === 1 && (\" \" + this[i].className + \" \").replace(rclass, \" \").indexOf( className ) >= 0 ) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n});\n\n\n\n\nvar rreturn = /\\r/g;\n\njQuery.fn.extend({\n\tval: function( value ) {\n\t\tvar hooks, ret, isFunction,\n\t\t\telem = this[0];\n\n\t\tif ( !arguments.length ) {\n\t\t\tif ( elem ) {\n\t\t\t\thooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];\n\n\t\t\t\tif ( hooks && \"get\" in hooks && (ret = hooks.get( elem, \"value\" )) !== undefined ) {\n\t\t\t\t\treturn ret;\n\t\t\t\t}\n\n\t\t\t\tret = elem.value;\n\n\t\t\t\treturn typeof ret === \"string\" ?\n\t\t\t\t\t// Handle most common string cases\n\t\t\t\t\tret.replace(rreturn, \"\") :\n\t\t\t\t\t// Handle cases where value is null/undef or number\n\t\t\t\t\tret == null ? \"\" : ret;\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tisFunction = jQuery.isFunction( value );\n\n\t\treturn this.each(function( i ) {\n\t\t\tvar val;\n\n\t\t\tif ( this.nodeType !== 1 ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif ( isFunction ) {\n\t\t\t\tval = value.call( this, i, jQuery( this ).val() );\n\t\t\t} else {\n\t\t\t\tval = value;\n\t\t\t}\n\n\t\t\t// Treat null/undefined as \"\"; convert numbers to string\n\t\t\tif ( val == null ) {\n\t\t\t\tval = \"\";\n\n\t\t\t} else if ( typeof val === \"number\" ) {\n\t\t\t\tval += \"\";\n\n\t\t\t} else if ( jQuery.isArray( val ) ) {\n\t\t\t\tval = jQuery.map( val, function( value ) {\n\t\t\t\t\treturn value == null ? \"\" : value + \"\";\n\t\t\t\t});\n\t\t\t}\n\n\t\t\thooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];\n\n\t\t\t// If set returns undefined, fall back to normal setting\n\t\t\tif ( !hooks || !(\"set\" in hooks) || hooks.set( this, val, \"value\" ) === undefined ) {\n\t\t\t\tthis.value = val;\n\t\t\t}\n\t\t});\n\t}\n});\n\njQuery.extend({\n\tvalHooks: {\n\t\toption: {\n\t\t\tget: function( elem ) {\n\t\t\t\tvar val = jQuery.find.attr( elem, \"value\" );\n\t\t\t\treturn val != null ?\n\t\t\t\t\tval :\n\t\t\t\t\t// Support: IE10-11+\n\t\t\t\t\t// option.text throws exceptions (#14686, #14858)\n\t\t\t\t\tjQuery.trim( jQuery.text( elem ) );\n\t\t\t}\n\t\t},\n\t\tselect: {\n\t\t\tget: function( elem ) {\n\t\t\t\tvar value, option,\n\t\t\t\t\toptions = elem.options,\n\t\t\t\t\tindex = elem.selectedIndex,\n\t\t\t\t\tone = elem.type === \"select-one\" || index < 0,\n\t\t\t\t\tvalues = one ? null : [],\n\t\t\t\t\tmax = one ? index + 1 : options.length,\n\t\t\t\t\ti = index < 0 ?\n\t\t\t\t\t\tmax :\n\t\t\t\t\t\tone ? index : 0;\n\n\t\t\t\t// Loop through all the selected options\n\t\t\t\tfor ( ; i < max; i++ ) {\n\t\t\t\t\toption = options[ i ];\n\n\t\t\t\t\t// IE6-9 doesn't update selected after form reset (#2551)\n\t\t\t\t\tif ( ( option.selected || i === index ) &&\n\t\t\t\t\t\t\t// Don't return options that are disabled or in a disabled optgroup\n\t\t\t\t\t\t\t( support.optDisabled ? !option.disabled : option.getAttribute( \"disabled\" ) === null ) &&\n\t\t\t\t\t\t\t( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, \"optgroup\" ) ) ) {\n\n\t\t\t\t\t\t// Get the specific value for the option\n\t\t\t\t\t\tvalue = jQuery( option ).val();\n\n\t\t\t\t\t\t// We don't need an array for one selects\n\t\t\t\t\t\tif ( one ) {\n\t\t\t\t\t\t\treturn value;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Multi-Selects return an array\n\t\t\t\t\t\tvalues.push( value );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn values;\n\t\t\t},\n\n\t\t\tset: function( elem, value ) {\n\t\t\t\tvar optionSet, option,\n\t\t\t\t\toptions = elem.options,\n\t\t\t\t\tvalues = jQuery.makeArray( value ),\n\t\t\t\t\ti = options.length;\n\n\t\t\t\twhile ( i-- ) {\n\t\t\t\t\toption = options[ i ];\n\t\t\t\t\tif ( (option.selected = jQuery.inArray( option.value, values ) >= 0) ) {\n\t\t\t\t\t\toptionSet = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Force browsers to behave consistently when non-matching value is set\n\t\t\t\tif ( !optionSet ) {\n\t\t\t\t\telem.selectedIndex = -1;\n\t\t\t\t}\n\t\t\t\treturn values;\n\t\t\t}\n\t\t}\n\t}\n});\n\n// Radios and checkboxes getter/setter\njQuery.each([ \"radio\", \"checkbox\" ], function() {\n\tjQuery.valHooks[ this ] = {\n\t\tset: function( elem, value ) {\n\t\t\tif ( jQuery.isArray( value ) ) {\n\t\t\t\treturn ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );\n\t\t\t}\n\t\t}\n\t};\n\tif ( !support.checkOn ) {\n\t\tjQuery.valHooks[ this ].get = function( elem ) {\n\t\t\treturn elem.getAttribute(\"value\") === null ? \"on\" : elem.value;\n\t\t};\n\t}\n});\n\n\n\n\n// Return jQuery for attributes-only inclusion\n\n\njQuery.each( (\"blur focus focusin focusout load resize scroll unload click dblclick \" +\n\t\"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave \" +\n\t\"change select submit keydown keypress keyup error contextmenu\").split(\" \"), function( i, name ) {\n\n\t// Handle event binding\n\tjQuery.fn[ name ] = function( data, fn ) {\n\t\treturn arguments.length > 0 ?\n\t\t\tthis.on( name, null, data, fn ) :\n\t\t\tthis.trigger( name );\n\t};\n});\n\njQuery.fn.extend({\n\thover: function( fnOver, fnOut ) {\n\t\treturn this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );\n\t},\n\n\tbind: function( types, data, fn ) {\n\t\treturn this.on( types, null, data, fn );\n\t},\n\tunbind: function( types, fn ) {\n\t\treturn this.off( types, null, fn );\n\t},\n\n\tdelegate: function( selector, types, data, fn ) {\n\t\treturn this.on( types, selector, data, fn );\n\t},\n\tundelegate: function( selector, types, fn ) {\n\t\t// ( namespace ) or ( selector, types [, fn] )\n\t\treturn arguments.length === 1 ? this.off( selector, \"**\" ) : this.off( types, selector || \"**\", fn );\n\t}\n});\n\n\nvar nonce = jQuery.now();\n\nvar rquery = (/\\?/);\n\n\n\n// Support: Android 2.3\n// Workaround failure to string-cast null input\njQuery.parseJSON = function( data ) {\n\treturn JSON.parse( data + \"\" );\n};\n\n\n// Cross-browser xml parsing\njQuery.parseXML = function( data ) {\n\tvar xml, tmp;\n\tif ( !data || typeof data !== \"string\" ) {\n\t\treturn null;\n\t}\n\n\t// Support: IE9\n\ttry {\n\t\ttmp = new DOMParser();\n\t\txml = tmp.parseFromString( data, \"text/xml\" );\n\t} catch ( e ) {\n\t\txml = undefined;\n\t}\n\n\tif ( !xml || xml.getElementsByTagName( \"parsererror\" ).length ) {\n\t\tjQuery.error( \"Invalid XML: \" + data );\n\t}\n\treturn xml;\n};\n\n\nvar\n\trhash = /#.*$/,\n\trts = /([?&])_=[^&]*/,\n\trheaders = /^(.*?):[ \\t]*([^\\r\\n]*)$/mg,\n\t// #7653, #8125, #8152: local protocol detection\n\trlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,\n\trnoContent = /^(?:GET|HEAD)$/,\n\trprotocol = /^\\/\\//,\n\trurl = /^([\\w.+-]+:)(?:\\/\\/(?:[^\\/?#]*@|)([^\\/?#:]*)(?::(\\d+)|)|)/,\n\n\t/* Prefilters\n\t * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)\n\t * 2) These are called:\n\t *    - BEFORE asking for a transport\n\t *    - AFTER param serialization (s.data is a string if s.processData is true)\n\t * 3) key is the dataType\n\t * 4) the catchall symbol \"*\" can be used\n\t * 5) execution will start with transport dataType and THEN continue down to \"*\" if needed\n\t */\n\tprefilters = {},\n\n\t/* Transports bindings\n\t * 1) key is the dataType\n\t * 2) the catchall symbol \"*\" can be used\n\t * 3) selection will start with transport dataType and THEN go to \"*\" if needed\n\t */\n\ttransports = {},\n\n\t// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression\n\tallTypes = \"*/\".concat( \"*\" ),\n\n\t// Document location\n\tajaxLocation = window.location.href,\n\n\t// Segment location into parts\n\tajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];\n\n// Base \"constructor\" for jQuery.ajaxPrefilter and jQuery.ajaxTransport\nfunction addToPrefiltersOrTransports( structure ) {\n\n\t// dataTypeExpression is optional and defaults to \"*\"\n\treturn function( dataTypeExpression, func ) {\n\n\t\tif ( typeof dataTypeExpression !== \"string\" ) {\n\t\t\tfunc = dataTypeExpression;\n\t\t\tdataTypeExpression = \"*\";\n\t\t}\n\n\t\tvar dataType,\n\t\t\ti = 0,\n\t\t\tdataTypes = dataTypeExpression.toLowerCase().match( rnotwhite ) || [];\n\n\t\tif ( jQuery.isFunction( func ) ) {\n\t\t\t// For each dataType in the dataTypeExpression\n\t\t\twhile ( (dataType = dataTypes[i++]) ) {\n\t\t\t\t// Prepend if requested\n\t\t\t\tif ( dataType[0] === \"+\" ) {\n\t\t\t\t\tdataType = dataType.slice( 1 ) || \"*\";\n\t\t\t\t\t(structure[ dataType ] = structure[ dataType ] || []).unshift( func );\n\n\t\t\t\t// Otherwise append\n\t\t\t\t} else {\n\t\t\t\t\t(structure[ dataType ] = structure[ dataType ] || []).push( func );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n}\n\n// Base inspection function for prefilters and transports\nfunction inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {\n\n\tvar inspected = {},\n\t\tseekingTransport = ( structure === transports );\n\n\tfunction inspect( dataType ) {\n\t\tvar selected;\n\t\tinspected[ dataType ] = true;\n\t\tjQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {\n\t\t\tvar dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );\n\t\t\tif ( typeof dataTypeOrTransport === \"string\" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {\n\t\t\t\toptions.dataTypes.unshift( dataTypeOrTransport );\n\t\t\t\tinspect( dataTypeOrTransport );\n\t\t\t\treturn false;\n\t\t\t} else if ( seekingTransport ) {\n\t\t\t\treturn !( selected = dataTypeOrTransport );\n\t\t\t}\n\t\t});\n\t\treturn selected;\n\t}\n\n\treturn inspect( options.dataTypes[ 0 ] ) || !inspected[ \"*\" ] && inspect( \"*\" );\n}\n\n// A special extend for ajax options\n// that takes \"flat\" options (not to be deep extended)\n// Fixes #9887\nfunction ajaxExtend( target, src ) {\n\tvar key, deep,\n\t\tflatOptions = jQuery.ajaxSettings.flatOptions || {};\n\n\tfor ( key in src ) {\n\t\tif ( src[ key ] !== undefined ) {\n\t\t\t( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ];\n\t\t}\n\t}\n\tif ( deep ) {\n\t\tjQuery.extend( true, target, deep );\n\t}\n\n\treturn target;\n}\n\n/* Handles responses to an ajax request:\n * - finds the right dataType (mediates between content-type and expected dataType)\n * - returns the corresponding response\n */\nfunction ajaxHandleResponses( s, jqXHR, responses ) {\n\n\tvar ct, type, finalDataType, firstDataType,\n\t\tcontents = s.contents,\n\t\tdataTypes = s.dataTypes;\n\n\t// Remove auto dataType and get content-type in the process\n\twhile ( dataTypes[ 0 ] === \"*\" ) {\n\t\tdataTypes.shift();\n\t\tif ( ct === undefined ) {\n\t\t\tct = s.mimeType || jqXHR.getResponseHeader(\"Content-Type\");\n\t\t}\n\t}\n\n\t// Check if we're dealing with a known content-type\n\tif ( ct ) {\n\t\tfor ( type in contents ) {\n\t\t\tif ( contents[ type ] && contents[ type ].test( ct ) ) {\n\t\t\t\tdataTypes.unshift( type );\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check to see if we have a response for the expected dataType\n\tif ( dataTypes[ 0 ] in responses ) {\n\t\tfinalDataType = dataTypes[ 0 ];\n\t} else {\n\t\t// Try convertible dataTypes\n\t\tfor ( type in responses ) {\n\t\t\tif ( !dataTypes[ 0 ] || s.converters[ type + \" \" + dataTypes[0] ] ) {\n\t\t\t\tfinalDataType = type;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif ( !firstDataType ) {\n\t\t\t\tfirstDataType = type;\n\t\t\t}\n\t\t}\n\t\t// Or just use first one\n\t\tfinalDataType = finalDataType || firstDataType;\n\t}\n\n\t// If we found a dataType\n\t// We add the dataType to the list if needed\n\t// and return the corresponding response\n\tif ( finalDataType ) {\n\t\tif ( finalDataType !== dataTypes[ 0 ] ) {\n\t\t\tdataTypes.unshift( finalDataType );\n\t\t}\n\t\treturn responses[ finalDataType ];\n\t}\n}\n\n/* Chain conversions given the request and the original response\n * Also sets the responseXXX fields on the jqXHR instance\n */\nfunction ajaxConvert( s, response, jqXHR, isSuccess ) {\n\tvar conv2, current, conv, tmp, prev,\n\t\tconverters = {},\n\t\t// Work with a copy of dataTypes in case we need to modify it for conversion\n\t\tdataTypes = s.dataTypes.slice();\n\n\t// Create converters map with lowercased keys\n\tif ( dataTypes[ 1 ] ) {\n\t\tfor ( conv in s.converters ) {\n\t\t\tconverters[ conv.toLowerCase() ] = s.converters[ conv ];\n\t\t}\n\t}\n\n\tcurrent = dataTypes.shift();\n\n\t// Convert to each sequential dataType\n\twhile ( current ) {\n\n\t\tif ( s.responseFields[ current ] ) {\n\t\t\tjqXHR[ s.responseFields[ current ] ] = response;\n\t\t}\n\n\t\t// Apply the dataFilter if provided\n\t\tif ( !prev && isSuccess && s.dataFilter ) {\n\t\t\tresponse = s.dataFilter( response, s.dataType );\n\t\t}\n\n\t\tprev = current;\n\t\tcurrent = dataTypes.shift();\n\n\t\tif ( current ) {\n\n\t\t// There's only work to do if current dataType is non-auto\n\t\t\tif ( current === \"*\" ) {\n\n\t\t\t\tcurrent = prev;\n\n\t\t\t// Convert response if prev dataType is non-auto and differs from current\n\t\t\t} else if ( prev !== \"*\" && prev !== current ) {\n\n\t\t\t\t// Seek a direct converter\n\t\t\t\tconv = converters[ prev + \" \" + current ] || converters[ \"* \" + current ];\n\n\t\t\t\t// If none found, seek a pair\n\t\t\t\tif ( !conv ) {\n\t\t\t\t\tfor ( conv2 in converters ) {\n\n\t\t\t\t\t\t// If conv2 outputs current\n\t\t\t\t\t\ttmp = conv2.split( \" \" );\n\t\t\t\t\t\tif ( tmp[ 1 ] === current ) {\n\n\t\t\t\t\t\t\t// If prev can be converted to accepted input\n\t\t\t\t\t\t\tconv = converters[ prev + \" \" + tmp[ 0 ] ] ||\n\t\t\t\t\t\t\t\tconverters[ \"* \" + tmp[ 0 ] ];\n\t\t\t\t\t\t\tif ( conv ) {\n\t\t\t\t\t\t\t\t// Condense equivalence converters\n\t\t\t\t\t\t\t\tif ( conv === true ) {\n\t\t\t\t\t\t\t\t\tconv = converters[ conv2 ];\n\n\t\t\t\t\t\t\t\t// Otherwise, insert the intermediate dataType\n\t\t\t\t\t\t\t\t} else if ( converters[ conv2 ] !== true ) {\n\t\t\t\t\t\t\t\t\tcurrent = tmp[ 0 ];\n\t\t\t\t\t\t\t\t\tdataTypes.unshift( tmp[ 1 ] );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Apply converter (if not an equivalence)\n\t\t\t\tif ( conv !== true ) {\n\n\t\t\t\t\t// Unless errors are allowed to bubble, catch and return them\n\t\t\t\t\tif ( conv && s[ \"throws\" ] ) {\n\t\t\t\t\t\tresponse = conv( response );\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tresponse = conv( response );\n\t\t\t\t\t\t} catch ( e ) {\n\t\t\t\t\t\t\treturn { state: \"parsererror\", error: conv ? e : \"No conversion from \" + prev + \" to \" + current };\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { state: \"success\", data: response };\n}\n\njQuery.extend({\n\n\t// Counter for holding the number of active queries\n\tactive: 0,\n\n\t// Last-Modified header cache for next request\n\tlastModified: {},\n\tetag: {},\n\n\tajaxSettings: {\n\t\turl: ajaxLocation,\n\t\ttype: \"GET\",\n\t\tisLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),\n\t\tglobal: true,\n\t\tprocessData: true,\n\t\tasync: true,\n\t\tcontentType: \"application/x-www-form-urlencoded; charset=UTF-8\",\n\t\t/*\n\t\ttimeout: 0,\n\t\tdata: null,\n\t\tdataType: null,\n\t\tusername: null,\n\t\tpassword: null,\n\t\tcache: null,\n\t\tthrows: false,\n\t\ttraditional: false,\n\t\theaders: {},\n\t\t*/\n\n\t\taccepts: {\n\t\t\t\"*\": allTypes,\n\t\t\ttext: \"text/plain\",\n\t\t\thtml: \"text/html\",\n\t\t\txml: \"application/xml, text/xml\",\n\t\t\tjson: \"application/json, text/javascript\"\n\t\t},\n\n\t\tcontents: {\n\t\t\txml: /xml/,\n\t\t\thtml: /html/,\n\t\t\tjson: /json/\n\t\t},\n\n\t\tresponseFields: {\n\t\t\txml: \"responseXML\",\n\t\t\ttext: \"responseText\",\n\t\t\tjson: \"responseJSON\"\n\t\t},\n\n\t\t// Data converters\n\t\t// Keys separate source (or catchall \"*\") and destination types with a single space\n\t\tconverters: {\n\n\t\t\t// Convert anything to text\n\t\t\t\"* text\": String,\n\n\t\t\t// Text to html (true = no transformation)\n\t\t\t\"text html\": true,\n\n\t\t\t// Evaluate text as a json expression\n\t\t\t\"text json\": jQuery.parseJSON,\n\n\t\t\t// Parse text as xml\n\t\t\t\"text xml\": jQuery.parseXML\n\t\t},\n\n\t\t// For options that shouldn't be deep extended:\n\t\t// you can add your own custom options here if\n\t\t// and when you create one that shouldn't be\n\t\t// deep extended (see ajaxExtend)\n\t\tflatOptions: {\n\t\t\turl: true,\n\t\t\tcontext: true\n\t\t}\n\t},\n\n\t// Creates a full fledged settings object into target\n\t// with both ajaxSettings and settings fields.\n\t// If target is omitted, writes into ajaxSettings.\n\tajaxSetup: function( target, settings ) {\n\t\treturn settings ?\n\n\t\t\t// Building a settings object\n\t\t\tajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :\n\n\t\t\t// Extending ajaxSettings\n\t\t\tajaxExtend( jQuery.ajaxSettings, target );\n\t},\n\n\tajaxPrefilter: addToPrefiltersOrTransports( prefilters ),\n\tajaxTransport: addToPrefiltersOrTransports( transports ),\n\n\t// Main method\n\tajax: function( url, options ) {\n\n\t\t// If url is an object, simulate pre-1.5 signature\n\t\tif ( typeof url === \"object\" ) {\n\t\t\toptions = url;\n\t\t\turl = undefined;\n\t\t}\n\n\t\t// Force options to be an object\n\t\toptions = options || {};\n\n\t\tvar transport,\n\t\t\t// URL without anti-cache param\n\t\t\tcacheURL,\n\t\t\t// Response headers\n\t\t\tresponseHeadersString,\n\t\t\tresponseHeaders,\n\t\t\t// timeout handle\n\t\t\ttimeoutTimer,\n\t\t\t// Cross-domain detection vars\n\t\t\tparts,\n\t\t\t// To know if global events are to be dispatched\n\t\t\tfireGlobals,\n\t\t\t// Loop variable\n\t\t\ti,\n\t\t\t// Create the final options object\n\t\t\ts = jQuery.ajaxSetup( {}, options ),\n\t\t\t// Callbacks context\n\t\t\tcallbackContext = s.context || s,\n\t\t\t// Context for global events is callbackContext if it is a DOM node or jQuery collection\n\t\t\tglobalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?\n\t\t\t\tjQuery( callbackContext ) :\n\t\t\t\tjQuery.event,\n\t\t\t// Deferreds\n\t\t\tdeferred = jQuery.Deferred(),\n\t\t\tcompleteDeferred = jQuery.Callbacks(\"once memory\"),\n\t\t\t// Status-dependent callbacks\n\t\t\tstatusCode = s.statusCode || {},\n\t\t\t// Headers (they are sent all at once)\n\t\t\trequestHeaders = {},\n\t\t\trequestHeadersNames = {},\n\t\t\t// The jqXHR state\n\t\t\tstate = 0,\n\t\t\t// Default abort message\n\t\t\tstrAbort = \"canceled\",\n\t\t\t// Fake xhr\n\t\t\tjqXHR = {\n\t\t\t\treadyState: 0,\n\n\t\t\t\t// Builds headers hashtable if needed\n\t\t\t\tgetResponseHeader: function( key ) {\n\t\t\t\t\tvar match;\n\t\t\t\t\tif ( state === 2 ) {\n\t\t\t\t\t\tif ( !responseHeaders ) {\n\t\t\t\t\t\t\tresponseHeaders = {};\n\t\t\t\t\t\t\twhile ( (match = rheaders.exec( responseHeadersString )) ) {\n\t\t\t\t\t\t\t\tresponseHeaders[ match[1].toLowerCase() ] = match[ 2 ];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmatch = responseHeaders[ key.toLowerCase() ];\n\t\t\t\t\t}\n\t\t\t\t\treturn match == null ? null : match;\n\t\t\t\t},\n\n\t\t\t\t// Raw string\n\t\t\t\tgetAllResponseHeaders: function() {\n\t\t\t\t\treturn state === 2 ? responseHeadersString : null;\n\t\t\t\t},\n\n\t\t\t\t// Caches the header\n\t\t\t\tsetRequestHeader: function( name, value ) {\n\t\t\t\t\tvar lname = name.toLowerCase();\n\t\t\t\t\tif ( !state ) {\n\t\t\t\t\t\tname = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;\n\t\t\t\t\t\trequestHeaders[ name ] = value;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Overrides response content-type header\n\t\t\t\toverrideMimeType: function( type ) {\n\t\t\t\t\tif ( !state ) {\n\t\t\t\t\t\ts.mimeType = type;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Status-dependent callbacks\n\t\t\t\tstatusCode: function( map ) {\n\t\t\t\t\tvar code;\n\t\t\t\t\tif ( map ) {\n\t\t\t\t\t\tif ( state < 2 ) {\n\t\t\t\t\t\t\tfor ( code in map ) {\n\t\t\t\t\t\t\t\t// Lazy-add the new callback in a way that preserves old ones\n\t\t\t\t\t\t\t\tstatusCode[ code ] = [ statusCode[ code ], map[ code ] ];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Execute the appropriate callbacks\n\t\t\t\t\t\t\tjqXHR.always( map[ jqXHR.status ] );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Cancel the request\n\t\t\t\tabort: function( statusText ) {\n\t\t\t\t\tvar finalText = statusText || strAbort;\n\t\t\t\t\tif ( transport ) {\n\t\t\t\t\t\ttransport.abort( finalText );\n\t\t\t\t\t}\n\t\t\t\t\tdone( 0, finalText );\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t};\n\n\t\t// Attach deferreds\n\t\tdeferred.promise( jqXHR ).complete = completeDeferred.add;\n\t\tjqXHR.success = jqXHR.done;\n\t\tjqXHR.error = jqXHR.fail;\n\n\t\t// Remove hash character (#7531: and string promotion)\n\t\t// Add protocol if not provided (prefilters might expect it)\n\t\t// Handle falsy url in the settings object (#10093: consistency with old signature)\n\t\t// We also use the url parameter if available\n\t\ts.url = ( ( url || s.url || ajaxLocation ) + \"\" ).replace( rhash, \"\" )\n\t\t\t.replace( rprotocol, ajaxLocParts[ 1 ] + \"//\" );\n\n\t\t// Alias method option to type as per ticket #12004\n\t\ts.type = options.method || options.type || s.method || s.type;\n\n\t\t// Extract dataTypes list\n\t\ts.dataTypes = jQuery.trim( s.dataType || \"*\" ).toLowerCase().match( rnotwhite ) || [ \"\" ];\n\n\t\t// A cross-domain request is in order when we have a protocol:host:port mismatch\n\t\tif ( s.crossDomain == null ) {\n\t\t\tparts = rurl.exec( s.url.toLowerCase() );\n\t\t\ts.crossDomain = !!( parts &&\n\t\t\t\t( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||\n\t\t\t\t\t( parts[ 3 ] || ( parts[ 1 ] === \"http:\" ? \"80\" : \"443\" ) ) !==\n\t\t\t\t\t\t( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === \"http:\" ? \"80\" : \"443\" ) ) )\n\t\t\t);\n\t\t}\n\n\t\t// Convert data if not already a string\n\t\tif ( s.data && s.processData && typeof s.data !== \"string\" ) {\n\t\t\ts.data = jQuery.param( s.data, s.traditional );\n\t\t}\n\n\t\t// Apply prefilters\n\t\tinspectPrefiltersOrTransports( prefilters, s, options, jqXHR );\n\n\t\t// If request was aborted inside a prefilter, stop there\n\t\tif ( state === 2 ) {\n\t\t\treturn jqXHR;\n\t\t}\n\n\t\t// We can fire global events as of now if asked to\n\t\t// Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118)\n\t\tfireGlobals = jQuery.event && s.global;\n\n\t\t// Watch for a new set of requests\n\t\tif ( fireGlobals && jQuery.active++ === 0 ) {\n\t\t\tjQuery.event.trigger(\"ajaxStart\");\n\t\t}\n\n\t\t// Uppercase the type\n\t\ts.type = s.type.toUpperCase();\n\n\t\t// Determine if request has content\n\t\ts.hasContent = !rnoContent.test( s.type );\n\n\t\t// Save the URL in case we're toying with the If-Modified-Since\n\t\t// and/or If-None-Match header later on\n\t\tcacheURL = s.url;\n\n\t\t// More options handling for requests with no content\n\t\tif ( !s.hasContent ) {\n\n\t\t\t// If data is available, append data to url\n\t\t\tif ( s.data ) {\n\t\t\t\tcacheURL = ( s.url += ( rquery.test( cacheURL ) ? \"&\" : \"?\" ) + s.data );\n\t\t\t\t// #9682: remove data so that it's not used in an eventual retry\n\t\t\t\tdelete s.data;\n\t\t\t}\n\n\t\t\t// Add anti-cache in url if needed\n\t\t\tif ( s.cache === false ) {\n\t\t\t\ts.url = rts.test( cacheURL ) ?\n\n\t\t\t\t\t// If there is already a '_' parameter, set its value\n\t\t\t\t\tcacheURL.replace( rts, \"$1_=\" + nonce++ ) :\n\n\t\t\t\t\t// Otherwise add one to the end\n\t\t\t\t\tcacheURL + ( rquery.test( cacheURL ) ? \"&\" : \"?\" ) + \"_=\" + nonce++;\n\t\t\t}\n\t\t}\n\n\t\t// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.\n\t\tif ( s.ifModified ) {\n\t\t\tif ( jQuery.lastModified[ cacheURL ] ) {\n\t\t\t\tjqXHR.setRequestHeader( \"If-Modified-Since\", jQuery.lastModified[ cacheURL ] );\n\t\t\t}\n\t\t\tif ( jQuery.etag[ cacheURL ] ) {\n\t\t\t\tjqXHR.setRequestHeader( \"If-None-Match\", jQuery.etag[ cacheURL ] );\n\t\t\t}\n\t\t}\n\n\t\t// Set the correct header, if data is being sent\n\t\tif ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {\n\t\t\tjqXHR.setRequestHeader( \"Content-Type\", s.contentType );\n\t\t}\n\n\t\t// Set the Accepts header for the server, depending on the dataType\n\t\tjqXHR.setRequestHeader(\n\t\t\t\"Accept\",\n\t\t\ts.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?\n\t\t\t\ts.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== \"*\" ? \", \" + allTypes + \"; q=0.01\" : \"\" ) :\n\t\t\t\ts.accepts[ \"*\" ]\n\t\t);\n\n\t\t// Check for headers option\n\t\tfor ( i in s.headers ) {\n\t\t\tjqXHR.setRequestHeader( i, s.headers[ i ] );\n\t\t}\n\n\t\t// Allow custom headers/mimetypes and early abort\n\t\tif ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {\n\t\t\t// Abort if not done already and return\n\t\t\treturn jqXHR.abort();\n\t\t}\n\n\t\t// Aborting is no longer a cancellation\n\t\tstrAbort = \"abort\";\n\n\t\t// Install callbacks on deferreds\n\t\tfor ( i in { success: 1, error: 1, complete: 1 } ) {\n\t\t\tjqXHR[ i ]( s[ i ] );\n\t\t}\n\n\t\t// Get transport\n\t\ttransport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );\n\n\t\t// If no transport, we auto-abort\n\t\tif ( !transport ) {\n\t\t\tdone( -1, \"No Transport\" );\n\t\t} else {\n\t\t\tjqXHR.readyState = 1;\n\n\t\t\t// Send global event\n\t\t\tif ( fireGlobals ) {\n\t\t\t\tglobalEventContext.trigger( \"ajaxSend\", [ jqXHR, s ] );\n\t\t\t}\n\t\t\t// Timeout\n\t\t\tif ( s.async && s.timeout > 0 ) {\n\t\t\t\ttimeoutTimer = setTimeout(function() {\n\t\t\t\t\tjqXHR.abort(\"timeout\");\n\t\t\t\t}, s.timeout );\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tstate = 1;\n\t\t\t\ttransport.send( requestHeaders, done );\n\t\t\t} catch ( e ) {\n\t\t\t\t// Propagate exception as error if not done\n\t\t\t\tif ( state < 2 ) {\n\t\t\t\t\tdone( -1, e );\n\t\t\t\t// Simply rethrow otherwise\n\t\t\t\t} else {\n\t\t\t\t\tthrow e;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Callback for when everything is done\n\t\tfunction done( status, nativeStatusText, responses, headers ) {\n\t\t\tvar isSuccess, success, error, response, modified,\n\t\t\t\tstatusText = nativeStatusText;\n\n\t\t\t// Called once\n\t\t\tif ( state === 2 ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// State is \"done\" now\n\t\t\tstate = 2;\n\n\t\t\t// Clear timeout if it exists\n\t\t\tif ( timeoutTimer ) {\n\t\t\t\tclearTimeout( timeoutTimer );\n\t\t\t}\n\n\t\t\t// Dereference transport for early garbage collection\n\t\t\t// (no matter how long the jqXHR object will be used)\n\t\t\ttransport = undefined;\n\n\t\t\t// Cache response headers\n\t\t\tresponseHeadersString = headers || \"\";\n\n\t\t\t// Set readyState\n\t\t\tjqXHR.readyState = status > 0 ? 4 : 0;\n\n\t\t\t// Determine if successful\n\t\t\tisSuccess = status >= 200 && status < 300 || status === 304;\n\n\t\t\t// Get response data\n\t\t\tif ( responses ) {\n\t\t\t\tresponse = ajaxHandleResponses( s, jqXHR, responses );\n\t\t\t}\n\n\t\t\t// Convert no matter what (that way responseXXX fields are always set)\n\t\t\tresponse = ajaxConvert( s, response, jqXHR, isSuccess );\n\n\t\t\t// If successful, handle type chaining\n\t\t\tif ( isSuccess ) {\n\n\t\t\t\t// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.\n\t\t\t\tif ( s.ifModified ) {\n\t\t\t\t\tmodified = jqXHR.getResponseHeader(\"Last-Modified\");\n\t\t\t\t\tif ( modified ) {\n\t\t\t\t\t\tjQuery.lastModified[ cacheURL ] = modified;\n\t\t\t\t\t}\n\t\t\t\t\tmodified = jqXHR.getResponseHeader(\"etag\");\n\t\t\t\t\tif ( modified ) {\n\t\t\t\t\t\tjQuery.etag[ cacheURL ] = modified;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// if no content\n\t\t\t\tif ( status === 204 || s.type === \"HEAD\" ) {\n\t\t\t\t\tstatusText = \"nocontent\";\n\n\t\t\t\t// if not modified\n\t\t\t\t} else if ( status === 304 ) {\n\t\t\t\t\tstatusText = \"notmodified\";\n\n\t\t\t\t// If we have data, let's convert it\n\t\t\t\t} else {\n\t\t\t\t\tstatusText = response.state;\n\t\t\t\t\tsuccess = response.data;\n\t\t\t\t\terror = response.error;\n\t\t\t\t\tisSuccess = !error;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Extract error from statusText and normalize for non-aborts\n\t\t\t\terror = statusText;\n\t\t\t\tif ( status || !statusText ) {\n\t\t\t\t\tstatusText = \"error\";\n\t\t\t\t\tif ( status < 0 ) {\n\t\t\t\t\t\tstatus = 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Set data for the fake xhr object\n\t\t\tjqXHR.status = status;\n\t\t\tjqXHR.statusText = ( nativeStatusText || statusText ) + \"\";\n\n\t\t\t// Success/Error\n\t\t\tif ( isSuccess ) {\n\t\t\t\tdeferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );\n\t\t\t} else {\n\t\t\t\tdeferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );\n\t\t\t}\n\n\t\t\t// Status-dependent callbacks\n\t\t\tjqXHR.statusCode( statusCode );\n\t\t\tstatusCode = undefined;\n\n\t\t\tif ( fireGlobals ) {\n\t\t\t\tglobalEventContext.trigger( isSuccess ? \"ajaxSuccess\" : \"ajaxError\",\n\t\t\t\t\t[ jqXHR, s, isSuccess ? success : error ] );\n\t\t\t}\n\n\t\t\t// Complete\n\t\t\tcompleteDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );\n\n\t\t\tif ( fireGlobals ) {\n\t\t\t\tglobalEventContext.trigger( \"ajaxComplete\", [ jqXHR, s ] );\n\t\t\t\t// Handle the global AJAX counter\n\t\t\t\tif ( !( --jQuery.active ) ) {\n\t\t\t\t\tjQuery.event.trigger(\"ajaxStop\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn jqXHR;\n\t},\n\n\tgetJSON: function( url, data, callback ) {\n\t\treturn jQuery.get( url, data, callback, \"json\" );\n\t},\n\n\tgetScript: function( url, callback ) {\n\t\treturn jQuery.get( url, undefined, callback, \"script\" );\n\t}\n});\n\njQuery.each( [ \"get\", \"post\" ], function( i, method ) {\n\tjQuery[ method ] = function( url, data, callback, type ) {\n\t\t// Shift arguments if data argument was omitted\n\t\tif ( jQuery.isFunction( data ) ) {\n\t\t\ttype = type || callback;\n\t\t\tcallback = data;\n\t\t\tdata = undefined;\n\t\t}\n\n\t\treturn jQuery.ajax({\n\t\t\turl: url,\n\t\t\ttype: method,\n\t\t\tdataType: type,\n\t\t\tdata: data,\n\t\t\tsuccess: callback\n\t\t});\n\t};\n});\n\n\njQuery._evalUrl = function( url ) {\n\treturn jQuery.ajax({\n\t\turl: url,\n\t\ttype: \"GET\",\n\t\tdataType: \"script\",\n\t\tasync: false,\n\t\tglobal: false,\n\t\t\"throws\": true\n\t});\n};\n\n\njQuery.fn.extend({\n\twrapAll: function( html ) {\n\t\tvar wrap;\n\n\t\tif ( jQuery.isFunction( html ) ) {\n\t\t\treturn this.each(function( i ) {\n\t\t\t\tjQuery( this ).wrapAll( html.call(this, i) );\n\t\t\t});\n\t\t}\n\n\t\tif ( this[ 0 ] ) {\n\n\t\t\t// The elements to wrap the target around\n\t\t\twrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );\n\n\t\t\tif ( this[ 0 ].parentNode ) {\n\t\t\t\twrap.insertBefore( this[ 0 ] );\n\t\t\t}\n\n\t\t\twrap.map(function() {\n\t\t\t\tvar elem = this;\n\n\t\t\t\twhile ( elem.firstElementChild ) {\n\t\t\t\t\telem = elem.firstElementChild;\n\t\t\t\t}\n\n\t\t\t\treturn elem;\n\t\t\t}).append( this );\n\t\t}\n\n\t\treturn this;\n\t},\n\n\twrapInner: function( html ) {\n\t\tif ( jQuery.isFunction( html ) ) {\n\t\t\treturn this.each(function( i ) {\n\t\t\t\tjQuery( this ).wrapInner( html.call(this, i) );\n\t\t\t});\n\t\t}\n\n\t\treturn this.each(function() {\n\t\t\tvar self = jQuery( this ),\n\t\t\t\tcontents = self.contents();\n\n\t\t\tif ( contents.length ) {\n\t\t\t\tcontents.wrapAll( html );\n\n\t\t\t} else {\n\t\t\t\tself.append( html );\n\t\t\t}\n\t\t});\n\t},\n\n\twrap: function( html ) {\n\t\tvar isFunction = jQuery.isFunction( html );\n\n\t\treturn this.each(function( i ) {\n\t\t\tjQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );\n\t\t});\n\t},\n\n\tunwrap: function() {\n\t\treturn this.parent().each(function() {\n\t\t\tif ( !jQuery.nodeName( this, \"body\" ) ) {\n\t\t\t\tjQuery( this ).replaceWith( this.childNodes );\n\t\t\t}\n\t\t}).end();\n\t}\n});\n\n\njQuery.expr.filters.hidden = function( elem ) {\n\t// Support: Opera <= 12.12\n\t// Opera reports offsetWidths and offsetHeights less than zero on some elements\n\treturn elem.offsetWidth <= 0 && elem.offsetHeight <= 0;\n};\njQuery.expr.filters.visible = function( elem ) {\n\treturn !jQuery.expr.filters.hidden( elem );\n};\n\n\n\n\nvar r20 = /%20/g,\n\trbracket = /\\[\\]$/,\n\trCRLF = /\\r?\\n/g,\n\trsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,\n\trsubmittable = /^(?:input|select|textarea|keygen)/i;\n\nfunction buildParams( prefix, obj, traditional, add ) {\n\tvar name;\n\n\tif ( jQuery.isArray( obj ) ) {\n\t\t// Serialize array item.\n\t\tjQuery.each( obj, function( i, v ) {\n\t\t\tif ( traditional || rbracket.test( prefix ) ) {\n\t\t\t\t// Treat each array item as a scalar.\n\t\t\t\tadd( prefix, v );\n\n\t\t\t} else {\n\t\t\t\t// Item is non-scalar (array or object), encode its numeric index.\n\t\t\t\tbuildParams( prefix + \"[\" + ( typeof v === \"object\" ? i : \"\" ) + \"]\", v, traditional, add );\n\t\t\t}\n\t\t});\n\n\t} else if ( !traditional && jQuery.type( obj ) === \"object\" ) {\n\t\t// Serialize object item.\n\t\tfor ( name in obj ) {\n\t\t\tbuildParams( prefix + \"[\" + name + \"]\", obj[ name ], traditional, add );\n\t\t}\n\n\t} else {\n\t\t// Serialize scalar item.\n\t\tadd( prefix, obj );\n\t}\n}\n\n// Serialize an array of form elements or a set of\n// key/values into a query string\njQuery.param = function( a, traditional ) {\n\tvar prefix,\n\t\ts = [],\n\t\tadd = function( key, value ) {\n\t\t\t// If value is a function, invoke it and return its value\n\t\t\tvalue = jQuery.isFunction( value ) ? value() : ( value == null ? \"\" : value );\n\t\t\ts[ s.length ] = encodeURIComponent( key ) + \"=\" + encodeURIComponent( value );\n\t\t};\n\n\t// Set traditional to true for jQuery <= 1.3.2 behavior.\n\tif ( traditional === undefined ) {\n\t\ttraditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;\n\t}\n\n\t// If an array was passed in, assume that it is an array of form elements.\n\tif ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {\n\t\t// Serialize the form elements\n\t\tjQuery.each( a, function() {\n\t\t\tadd( this.name, this.value );\n\t\t});\n\n\t} else {\n\t\t// If traditional, encode the \"old\" way (the way 1.3.2 or older\n\t\t// did it), otherwise encode params recursively.\n\t\tfor ( prefix in a ) {\n\t\t\tbuildParams( prefix, a[ prefix ], traditional, add );\n\t\t}\n\t}\n\n\t// Return the resulting serialization\n\treturn s.join( \"&\" ).replace( r20, \"+\" );\n};\n\njQuery.fn.extend({\n\tserialize: function() {\n\t\treturn jQuery.param( this.serializeArray() );\n\t},\n\tserializeArray: function() {\n\t\treturn this.map(function() {\n\t\t\t// Can add propHook for \"elements\" to filter or add form elements\n\t\t\tvar elements = jQuery.prop( this, \"elements\" );\n\t\t\treturn elements ? jQuery.makeArray( elements ) : this;\n\t\t})\n\t\t.filter(function() {\n\t\t\tvar type = this.type;\n\n\t\t\t// Use .is( \":disabled\" ) so that fieldset[disabled] works\n\t\t\treturn this.name && !jQuery( this ).is( \":disabled\" ) &&\n\t\t\t\trsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&\n\t\t\t\t( this.checked || !rcheckableType.test( type ) );\n\t\t})\n\t\t.map(function( i, elem ) {\n\t\t\tvar val = jQuery( this ).val();\n\n\t\t\treturn val == null ?\n\t\t\t\tnull :\n\t\t\t\tjQuery.isArray( val ) ?\n\t\t\t\t\tjQuery.map( val, function( val ) {\n\t\t\t\t\t\treturn { name: elem.name, value: val.replace( rCRLF, \"\\r\\n\" ) };\n\t\t\t\t\t}) :\n\t\t\t\t\t{ name: elem.name, value: val.replace( rCRLF, \"\\r\\n\" ) };\n\t\t}).get();\n\t}\n});\n\n\njQuery.ajaxSettings.xhr = function() {\n\ttry {\n\t\treturn new XMLHttpRequest();\n\t} catch( e ) {}\n};\n\nvar xhrId = 0,\n\txhrCallbacks = {},\n\txhrSuccessStatus = {\n\t\t// file protocol always yields status code 0, assume 200\n\t\t0: 200,\n\t\t// Support: IE9\n\t\t// #1450: sometimes IE returns 1223 when it should be 204\n\t\t1223: 204\n\t},\n\txhrSupported = jQuery.ajaxSettings.xhr();\n\n// Support: IE9\n// Open requests must be manually aborted on unload (#5280)\n// See https://support.microsoft.com/kb/2856746 for more info\nif ( window.attachEvent ) {\n\twindow.attachEvent( \"onunload\", function() {\n\t\tfor ( var key in xhrCallbacks ) {\n\t\t\txhrCallbacks[ key ]();\n\t\t}\n\t});\n}\n\nsupport.cors = !!xhrSupported && ( \"withCredentials\" in xhrSupported );\nsupport.ajax = xhrSupported = !!xhrSupported;\n\njQuery.ajaxTransport(function( options ) {\n\tvar callback;\n\n\t// Cross domain only allowed if supported through XMLHttpRequest\n\tif ( support.cors || xhrSupported && !options.crossDomain ) {\n\t\treturn {\n\t\t\tsend: function( headers, complete ) {\n\t\t\t\tvar i,\n\t\t\t\t\txhr = options.xhr(),\n\t\t\t\t\tid = ++xhrId;\n\n\t\t\t\txhr.open( options.type, options.url, options.async, options.username, options.password );\n\n\t\t\t\t// Apply custom fields if provided\n\t\t\t\tif ( options.xhrFields ) {\n\t\t\t\t\tfor ( i in options.xhrFields ) {\n\t\t\t\t\t\txhr[ i ] = options.xhrFields[ i ];\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Override mime type if needed\n\t\t\t\tif ( options.mimeType && xhr.overrideMimeType ) {\n\t\t\t\t\txhr.overrideMimeType( options.mimeType );\n\t\t\t\t}\n\n\t\t\t\t// X-Requested-With header\n\t\t\t\t// For cross-domain requests, seeing as conditions for a preflight are\n\t\t\t\t// akin to a jigsaw puzzle, we simply never set it to be sure.\n\t\t\t\t// (it can always be set on a per-request basis or even using ajaxSetup)\n\t\t\t\t// For same-domain requests, won't change header if already provided.\n\t\t\t\tif ( !options.crossDomain && !headers[\"X-Requested-With\"] ) {\n\t\t\t\t\theaders[\"X-Requested-With\"] = \"XMLHttpRequest\";\n\t\t\t\t}\n\n\t\t\t\t// Set headers\n\t\t\t\tfor ( i in headers ) {\n\t\t\t\t\txhr.setRequestHeader( i, headers[ i ] );\n\t\t\t\t}\n\n\t\t\t\t// Callback\n\t\t\t\tcallback = function( type ) {\n\t\t\t\t\treturn function() {\n\t\t\t\t\t\tif ( callback ) {\n\t\t\t\t\t\t\tdelete xhrCallbacks[ id ];\n\t\t\t\t\t\t\tcallback = xhr.onload = xhr.onerror = null;\n\n\t\t\t\t\t\t\tif ( type === \"abort\" ) {\n\t\t\t\t\t\t\t\txhr.abort();\n\t\t\t\t\t\t\t} else if ( type === \"error\" ) {\n\t\t\t\t\t\t\t\tcomplete(\n\t\t\t\t\t\t\t\t\t// file: protocol always yields status 0; see #8605, #14207\n\t\t\t\t\t\t\t\t\txhr.status,\n\t\t\t\t\t\t\t\t\txhr.statusText\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcomplete(\n\t\t\t\t\t\t\t\t\txhrSuccessStatus[ xhr.status ] || xhr.status,\n\t\t\t\t\t\t\t\t\txhr.statusText,\n\t\t\t\t\t\t\t\t\t// Support: IE9\n\t\t\t\t\t\t\t\t\t// Accessing binary-data responseText throws an exception\n\t\t\t\t\t\t\t\t\t// (#11426)\n\t\t\t\t\t\t\t\t\ttypeof xhr.responseText === \"string\" ? {\n\t\t\t\t\t\t\t\t\t\ttext: xhr.responseText\n\t\t\t\t\t\t\t\t\t} : undefined,\n\t\t\t\t\t\t\t\t\txhr.getAllResponseHeaders()\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t};\n\n\t\t\t\t// Listen to events\n\t\t\t\txhr.onload = callback();\n\t\t\t\txhr.onerror = callback(\"error\");\n\n\t\t\t\t// Create the abort callback\n\t\t\t\tcallback = xhrCallbacks[ id ] = callback(\"abort\");\n\n\t\t\t\ttry {\n\t\t\t\t\t// Do send the request (this may raise an exception)\n\t\t\t\t\txhr.send( options.hasContent && options.data || null );\n\t\t\t\t} catch ( e ) {\n\t\t\t\t\t// #14683: Only rethrow if this hasn't been notified as an error yet\n\t\t\t\t\tif ( callback ) {\n\t\t\t\t\t\tthrow e;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tabort: function() {\n\t\t\t\tif ( callback ) {\n\t\t\t\t\tcallback();\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n});\n\n\n\n\n// Install script dataType\njQuery.ajaxSetup({\n\taccepts: {\n\t\tscript: \"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript\"\n\t},\n\tcontents: {\n\t\tscript: /(?:java|ecma)script/\n\t},\n\tconverters: {\n\t\t\"text script\": function( text ) {\n\t\t\tjQuery.globalEval( text );\n\t\t\treturn text;\n\t\t}\n\t}\n});\n\n// Handle cache's special case and crossDomain\njQuery.ajaxPrefilter( \"script\", function( s ) {\n\tif ( s.cache === undefined ) {\n\t\ts.cache = false;\n\t}\n\tif ( s.crossDomain ) {\n\t\ts.type = \"GET\";\n\t}\n});\n\n// Bind script tag hack transport\njQuery.ajaxTransport( \"script\", function( s ) {\n\t// This transport only deals with cross domain requests\n\tif ( s.crossDomain ) {\n\t\tvar script, callback;\n\t\treturn {\n\t\t\tsend: function( _, complete ) {\n\t\t\t\tscript = jQuery(\"<script>\").prop({\n\t\t\t\t\tasync: true,\n\t\t\t\t\tcharset: s.scriptCharset,\n\t\t\t\t\tsrc: s.url\n\t\t\t\t}).on(\n\t\t\t\t\t\"load error\",\n\t\t\t\t\tcallback = function( evt ) {\n\t\t\t\t\t\tscript.remove();\n\t\t\t\t\t\tcallback = null;\n\t\t\t\t\t\tif ( evt ) {\n\t\t\t\t\t\t\tcomplete( evt.type === \"error\" ? 404 : 200, evt.type );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t\tdocument.head.appendChild( script[ 0 ] );\n\t\t\t},\n\t\t\tabort: function() {\n\t\t\t\tif ( callback ) {\n\t\t\t\t\tcallback();\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n});\n\n\n\n\nvar oldCallbacks = [],\n\trjsonp = /(=)\\?(?=&|$)|\\?\\?/;\n\n// Default jsonp settings\njQuery.ajaxSetup({\n\tjsonp: \"callback\",\n\tjsonpCallback: function() {\n\t\tvar callback = oldCallbacks.pop() || ( jQuery.expando + \"_\" + ( nonce++ ) );\n\t\tthis[ callback ] = true;\n\t\treturn callback;\n\t}\n});\n\n// Detect, normalize options and install callbacks for jsonp requests\njQuery.ajaxPrefilter( \"json jsonp\", function( s, originalSettings, jqXHR ) {\n\n\tvar callbackName, overwritten, responseContainer,\n\t\tjsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?\n\t\t\t\"url\" :\n\t\t\ttypeof s.data === \"string\" && !( s.contentType || \"\" ).indexOf(\"application/x-www-form-urlencoded\") && rjsonp.test( s.data ) && \"data\"\n\t\t);\n\n\t// Handle iff the expected data type is \"jsonp\" or we have a parameter to set\n\tif ( jsonProp || s.dataTypes[ 0 ] === \"jsonp\" ) {\n\n\t\t// Get callback name, remembering preexisting value associated with it\n\t\tcallbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?\n\t\t\ts.jsonpCallback() :\n\t\t\ts.jsonpCallback;\n\n\t\t// Insert callback into url or form data\n\t\tif ( jsonProp ) {\n\t\t\ts[ jsonProp ] = s[ jsonProp ].replace( rjsonp, \"$1\" + callbackName );\n\t\t} else if ( s.jsonp !== false ) {\n\t\t\ts.url += ( rquery.test( s.url ) ? \"&\" : \"?\" ) + s.jsonp + \"=\" + callbackName;\n\t\t}\n\n\t\t// Use data converter to retrieve json after script execution\n\t\ts.converters[\"script json\"] = function() {\n\t\t\tif ( !responseContainer ) {\n\t\t\t\tjQuery.error( callbackName + \" was not called\" );\n\t\t\t}\n\t\t\treturn responseContainer[ 0 ];\n\t\t};\n\n\t\t// force json dataType\n\t\ts.dataTypes[ 0 ] = \"json\";\n\n\t\t// Install callback\n\t\toverwritten = window[ callbackName ];\n\t\twindow[ callbackName ] = function() {\n\t\t\tresponseContainer = arguments;\n\t\t};\n\n\t\t// Clean-up function (fires after converters)\n\t\tjqXHR.always(function() {\n\t\t\t// Restore preexisting value\n\t\t\twindow[ callbackName ] = overwritten;\n\n\t\t\t// Save back as free\n\t\t\tif ( s[ callbackName ] ) {\n\t\t\t\t// make sure that re-using the options doesn't screw things around\n\t\t\t\ts.jsonpCallback = originalSettings.jsonpCallback;\n\n\t\t\t\t// save the callback name for future use\n\t\t\t\toldCallbacks.push( callbackName );\n\t\t\t}\n\n\t\t\t// Call if it was a function and we have a response\n\t\t\tif ( responseContainer && jQuery.isFunction( overwritten ) ) {\n\t\t\t\toverwritten( responseContainer[ 0 ] );\n\t\t\t}\n\n\t\t\tresponseContainer = overwritten = undefined;\n\t\t});\n\n\t\t// Delegate to script\n\t\treturn \"script\";\n\t}\n});\n\n\n\n\n// data: string of html\n// context (optional): If specified, the fragment will be created in this context, defaults to document\n// keepScripts (optional): If true, will include scripts passed in the html string\njQuery.parseHTML = function( data, context, keepScripts ) {\n\tif ( !data || typeof data !== \"string\" ) {\n\t\treturn null;\n\t}\n\tif ( typeof context === \"boolean\" ) {\n\t\tkeepScripts = context;\n\t\tcontext = false;\n\t}\n\tcontext = context || document;\n\n\tvar parsed = rsingleTag.exec( data ),\n\t\tscripts = !keepScripts && [];\n\n\t// Single tag\n\tif ( parsed ) {\n\t\treturn [ context.createElement( parsed[1] ) ];\n\t}\n\n\tparsed = jQuery.buildFragment( [ data ], context, scripts );\n\n\tif ( scripts && scripts.length ) {\n\t\tjQuery( scripts ).remove();\n\t}\n\n\treturn jQuery.merge( [], parsed.childNodes );\n};\n\n\n// Keep a copy of the old load method\nvar _load = jQuery.fn.load;\n\n/**\n * Load a url into a page\n */\njQuery.fn.load = function( url, params, callback ) {\n\tif ( typeof url !== \"string\" && _load ) {\n\t\treturn _load.apply( this, arguments );\n\t}\n\n\tvar selector, type, response,\n\t\tself = this,\n\t\toff = url.indexOf(\" \");\n\n\tif ( off >= 0 ) {\n\t\tselector = jQuery.trim( url.slice( off ) );\n\t\turl = url.slice( 0, off );\n\t}\n\n\t// If it's a function\n\tif ( jQuery.isFunction( params ) ) {\n\n\t\t// We assume that it's the callback\n\t\tcallback = params;\n\t\tparams = undefined;\n\n\t// Otherwise, build a param string\n\t} else if ( params && typeof params === \"object\" ) {\n\t\ttype = \"POST\";\n\t}\n\n\t// If we have elements to modify, make the request\n\tif ( self.length > 0 ) {\n\t\tjQuery.ajax({\n\t\t\turl: url,\n\n\t\t\t// if \"type\" variable is undefined, then \"GET\" method will be used\n\t\t\ttype: type,\n\t\t\tdataType: \"html\",\n\t\t\tdata: params\n\t\t}).done(function( responseText ) {\n\n\t\t\t// Save response for use in complete callback\n\t\t\tresponse = arguments;\n\n\t\t\tself.html( selector ?\n\n\t\t\t\t// If a selector was specified, locate the right elements in a dummy div\n\t\t\t\t// Exclude scripts to avoid IE 'Permission Denied' errors\n\t\t\t\tjQuery(\"<div>\").append( jQuery.parseHTML( responseText ) ).find( selector ) :\n\n\t\t\t\t// Otherwise use the full result\n\t\t\t\tresponseText );\n\n\t\t}).complete( callback && function( jqXHR, status ) {\n\t\t\tself.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );\n\t\t});\n\t}\n\n\treturn this;\n};\n\n\n\n\n// Attach a bunch of functions for handling common AJAX events\njQuery.each( [ \"ajaxStart\", \"ajaxStop\", \"ajaxComplete\", \"ajaxError\", \"ajaxSuccess\", \"ajaxSend\" ], function( i, type ) {\n\tjQuery.fn[ type ] = function( fn ) {\n\t\treturn this.on( type, fn );\n\t};\n});\n\n\n\n\njQuery.expr.filters.animated = function( elem ) {\n\treturn jQuery.grep(jQuery.timers, function( fn ) {\n\t\treturn elem === fn.elem;\n\t}).length;\n};\n\n\n\n\nvar docElem = window.document.documentElement;\n\n/**\n * Gets a window from an element\n */\nfunction getWindow( elem ) {\n\treturn jQuery.isWindow( elem ) ? elem : elem.nodeType === 9 && elem.defaultView;\n}\n\njQuery.offset = {\n\tsetOffset: function( elem, options, i ) {\n\t\tvar curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,\n\t\t\tposition = jQuery.css( elem, \"position\" ),\n\t\t\tcurElem = jQuery( elem ),\n\t\t\tprops = {};\n\n\t\t// Set position first, in-case top/left are set even on static elem\n\t\tif ( position === \"static\" ) {\n\t\t\telem.style.position = \"relative\";\n\t\t}\n\n\t\tcurOffset = curElem.offset();\n\t\tcurCSSTop = jQuery.css( elem, \"top\" );\n\t\tcurCSSLeft = jQuery.css( elem, \"left\" );\n\t\tcalculatePosition = ( position === \"absolute\" || position === \"fixed\" ) &&\n\t\t\t( curCSSTop + curCSSLeft ).indexOf(\"auto\") > -1;\n\n\t\t// Need to be able to calculate position if either\n\t\t// top or left is auto and position is either absolute or fixed\n\t\tif ( calculatePosition ) {\n\t\t\tcurPosition = curElem.position();\n\t\t\tcurTop = curPosition.top;\n\t\t\tcurLeft = curPosition.left;\n\n\t\t} else {\n\t\t\tcurTop = parseFloat( curCSSTop ) || 0;\n\t\t\tcurLeft = parseFloat( curCSSLeft ) || 0;\n\t\t}\n\n\t\tif ( jQuery.isFunction( options ) ) {\n\t\t\toptions = options.call( elem, i, curOffset );\n\t\t}\n\n\t\tif ( options.top != null ) {\n\t\t\tprops.top = ( options.top - curOffset.top ) + curTop;\n\t\t}\n\t\tif ( options.left != null ) {\n\t\t\tprops.left = ( options.left - curOffset.left ) + curLeft;\n\t\t}\n\n\t\tif ( \"using\" in options ) {\n\t\t\toptions.using.call( elem, props );\n\n\t\t} else {\n\t\t\tcurElem.css( props );\n\t\t}\n\t}\n};\n\njQuery.fn.extend({\n\toffset: function( options ) {\n\t\tif ( arguments.length ) {\n\t\t\treturn options === undefined ?\n\t\t\t\tthis :\n\t\t\t\tthis.each(function( i ) {\n\t\t\t\t\tjQuery.offset.setOffset( this, options, i );\n\t\t\t\t});\n\t\t}\n\n\t\tvar docElem, win,\n\t\t\telem = this[ 0 ],\n\t\t\tbox = { top: 0, left: 0 },\n\t\t\tdoc = elem && elem.ownerDocument;\n\n\t\tif ( !doc ) {\n\t\t\treturn;\n\t\t}\n\n\t\tdocElem = doc.documentElement;\n\n\t\t// Make sure it's not a disconnected DOM node\n\t\tif ( !jQuery.contains( docElem, elem ) ) {\n\t\t\treturn box;\n\t\t}\n\n\t\t// Support: BlackBerry 5, iOS 3 (original iPhone)\n\t\t// If we don't have gBCR, just use 0,0 rather than error\n\t\tif ( typeof elem.getBoundingClientRect !== strundefined ) {\n\t\t\tbox = elem.getBoundingClientRect();\n\t\t}\n\t\twin = getWindow( doc );\n\t\treturn {\n\t\t\ttop: box.top + win.pageYOffset - docElem.clientTop,\n\t\t\tleft: box.left + win.pageXOffset - docElem.clientLeft\n\t\t};\n\t},\n\n\tposition: function() {\n\t\tif ( !this[ 0 ] ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar offsetParent, offset,\n\t\t\telem = this[ 0 ],\n\t\t\tparentOffset = { top: 0, left: 0 };\n\n\t\t// Fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is its only offset parent\n\t\tif ( jQuery.css( elem, \"position\" ) === \"fixed\" ) {\n\t\t\t// Assume getBoundingClientRect is there when computed position is fixed\n\t\t\toffset = elem.getBoundingClientRect();\n\n\t\t} else {\n\t\t\t// Get *real* offsetParent\n\t\t\toffsetParent = this.offsetParent();\n\n\t\t\t// Get correct offsets\n\t\t\toffset = this.offset();\n\t\t\tif ( !jQuery.nodeName( offsetParent[ 0 ], \"html\" ) ) {\n\t\t\t\tparentOffset = offsetParent.offset();\n\t\t\t}\n\n\t\t\t// Add offsetParent borders\n\t\t\tparentOffset.top += jQuery.css( offsetParent[ 0 ], \"borderTopWidth\", true );\n\t\t\tparentOffset.left += jQuery.css( offsetParent[ 0 ], \"borderLeftWidth\", true );\n\t\t}\n\n\t\t// Subtract parent offsets and element margins\n\t\treturn {\n\t\t\ttop: offset.top - parentOffset.top - jQuery.css( elem, \"marginTop\", true ),\n\t\t\tleft: offset.left - parentOffset.left - jQuery.css( elem, \"marginLeft\", true )\n\t\t};\n\t},\n\n\toffsetParent: function() {\n\t\treturn this.map(function() {\n\t\t\tvar offsetParent = this.offsetParent || docElem;\n\n\t\t\twhile ( offsetParent && ( !jQuery.nodeName( offsetParent, \"html\" ) && jQuery.css( offsetParent, \"position\" ) === \"static\" ) ) {\n\t\t\t\toffsetParent = offsetParent.offsetParent;\n\t\t\t}\n\n\t\t\treturn offsetParent || docElem;\n\t\t});\n\t}\n});\n\n// Create scrollLeft and scrollTop methods\njQuery.each( { scrollLeft: \"pageXOffset\", scrollTop: \"pageYOffset\" }, function( method, prop ) {\n\tvar top = \"pageYOffset\" === prop;\n\n\tjQuery.fn[ method ] = function( val ) {\n\t\treturn access( this, function( elem, method, val ) {\n\t\t\tvar win = getWindow( elem );\n\n\t\t\tif ( val === undefined ) {\n\t\t\t\treturn win ? win[ prop ] : elem[ method ];\n\t\t\t}\n\n\t\t\tif ( win ) {\n\t\t\t\twin.scrollTo(\n\t\t\t\t\t!top ? val : window.pageXOffset,\n\t\t\t\t\ttop ? val : window.pageYOffset\n\t\t\t\t);\n\n\t\t\t} else {\n\t\t\t\telem[ method ] = val;\n\t\t\t}\n\t\t}, method, val, arguments.length, null );\n\t};\n});\n\n// Support: Safari<7+, Chrome<37+\n// Add the top/left cssHooks using jQuery.fn.position\n// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084\n// Blink bug: https://code.google.com/p/chromium/issues/detail?id=229280\n// getComputedStyle returns percent when specified for top/left/bottom/right;\n// rather than make the css module depend on the offset module, just check for it here\njQuery.each( [ \"top\", \"left\" ], function( i, prop ) {\n\tjQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,\n\t\tfunction( elem, computed ) {\n\t\t\tif ( computed ) {\n\t\t\t\tcomputed = curCSS( elem, prop );\n\t\t\t\t// If curCSS returns percentage, fallback to offset\n\t\t\t\treturn rnumnonpx.test( computed ) ?\n\t\t\t\t\tjQuery( elem ).position()[ prop ] + \"px\" :\n\t\t\t\t\tcomputed;\n\t\t\t}\n\t\t}\n\t);\n});\n\n\n// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods\njQuery.each( { Height: \"height\", Width: \"width\" }, function( name, type ) {\n\tjQuery.each( { padding: \"inner\" + name, content: type, \"\": \"outer\" + name }, function( defaultExtra, funcName ) {\n\t\t// Margin is only for outerHeight, outerWidth\n\t\tjQuery.fn[ funcName ] = function( margin, value ) {\n\t\t\tvar chainable = arguments.length && ( defaultExtra || typeof margin !== \"boolean\" ),\n\t\t\t\textra = defaultExtra || ( margin === true || value === true ? \"margin\" : \"border\" );\n\n\t\t\treturn access( this, function( elem, type, value ) {\n\t\t\t\tvar doc;\n\n\t\t\t\tif ( jQuery.isWindow( elem ) ) {\n\t\t\t\t\t// As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there\n\t\t\t\t\t// isn't a whole lot we can do. See pull request at this URL for discussion:\n\t\t\t\t\t// https://github.com/jquery/jquery/pull/764\n\t\t\t\t\treturn elem.document.documentElement[ \"client\" + name ];\n\t\t\t\t}\n\n\t\t\t\t// Get document width or height\n\t\t\t\tif ( elem.nodeType === 9 ) {\n\t\t\t\t\tdoc = elem.documentElement;\n\n\t\t\t\t\t// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],\n\t\t\t\t\t// whichever is greatest\n\t\t\t\t\treturn Math.max(\n\t\t\t\t\t\telem.body[ \"scroll\" + name ], doc[ \"scroll\" + name ],\n\t\t\t\t\t\telem.body[ \"offset\" + name ], doc[ \"offset\" + name ],\n\t\t\t\t\t\tdoc[ \"client\" + name ]\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\treturn value === undefined ?\n\t\t\t\t\t// Get width or height on the element, requesting but not forcing parseFloat\n\t\t\t\t\tjQuery.css( elem, type, extra ) :\n\n\t\t\t\t\t// Set width or height on the element\n\t\t\t\t\tjQuery.style( elem, type, value, extra );\n\t\t\t}, type, chainable ? margin : undefined, chainable, null );\n\t\t};\n\t});\n});\n\n\n// The number of elements contained in the matched element set\njQuery.fn.size = function() {\n\treturn this.length;\n};\n\njQuery.fn.andSelf = jQuery.fn.addBack;\n\n\n\n\n// Register as a named AMD module, since jQuery can be concatenated with other\n// files that may use define, but not via a proper concatenation script that\n// understands anonymous AMD modules. A named AMD is safest and most robust\n// way to register. Lowercase jquery is used because AMD module names are\n// derived from file names, and jQuery is normally delivered in a lowercase\n// file name. Do this after creating the global so that if an AMD module wants\n// to call noConflict to hide this version of jQuery, it will work.\n\n// Note that for maximum portability, libraries that are not jQuery should\n// declare themselves as anonymous modules, and avoid setting a global if an\n// AMD loader is present. jQuery is a special case. For more information, see\n// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon\n\nif ( typeof define === \"function\" && define.amd ) {\n\tdefine( \"jquery\", [], function() {\n\t\treturn jQuery;\n\t});\n}\n\n\n\n\nvar\n\t// Map over jQuery in case of overwrite\n\t_jQuery = window.jQuery,\n\n\t// Map over the $ in case of overwrite\n\t_$ = window.$;\n\njQuery.noConflict = function( deep ) {\n\tif ( window.$ === jQuery ) {\n\t\twindow.$ = _$;\n\t}\n\n\tif ( deep && window.jQuery === jQuery ) {\n\t\twindow.jQuery = _jQuery;\n\t}\n\n\treturn jQuery;\n};\n\n// Expose jQuery and $ identifiers, even in AMD\n// (#7102#comment:10, https://github.com/jquery/jquery/pull/557)\n// and CommonJS for browser emulators (#13566)\nif ( typeof noGlobal === strundefined ) {\n\twindow.jQuery = window.$ = jQuery;\n}\n\n\n\n\nreturn jQuery;\n\n}));\n"
  },
  {
    "path": "js/jquery.nav.js",
    "content": "/*\n * jQuery One Page Nav Plugin\n * http://github.com/davist11/jQuery-One-Page-Nav\n *\n * Copyright (c) 2010 Trevor Davis (http://trevordavis.net)\n * Dual licensed under the MIT and GPL licenses.\n * Uses the same license as jQuery, see:\n * http://jquery.org/license\n *\n * @version 3.0.0\n *\n * Example usage:\n * $('#nav').onePageNav({\n *   currentClass: 'current',\n *   changeHash: false,\n *   scrollSpeed: 750\n * });\n */\n\n;(function($, window, document, undefined){\n\n\t// our plugin constructor\n\tvar OnePageNav = function(elem, options){\n\t\tthis.elem = elem;\n\t\tthis.$elem = $(elem);\n\t\tthis.options = options;\n\t\tthis.metadata = this.$elem.data('plugin-options');\n\t\tthis.$win = $(window);\n\t\tthis.sections = {};\n\t\tthis.didScroll = false;\n\t\tthis.$doc = $(document);\n\t\tthis.docHeight = this.$doc.height();\n\t};\n\n\t// the plugin prototype\n\tOnePageNav.prototype = {\n\t\tdefaults: {\n\t\t\tnavItems: 'a',\n\t\t\tcurrentClass: 'current',\n\t\t\tchangeHash: false,\n\t\t\teasing: 'swing',\n\t\t\tfilter: '',\n\t\t\tscrollSpeed: 750,\n\t\t\tscrollThreshold: 0.5,\n\t\t\tbegin: false,\n\t\t\tend: false,\n\t\t\tscrollChange: false,\n\t\t\tpadding: 0\n\t\t},\n\n\t\tinit: function() {\n\t\t\t// Introduce defaults that can be extended either\n\t\t\t// globally or using an object literal.\n\t\t\tthis.config = $.extend({}, this.defaults, this.options, this.metadata);\n\n\t\t\tthis.$nav = this.$elem.find(this.config.navItems);\n\n\t\t\t//Filter any links out of the nav\n\t\t\tif(this.config.filter !== '') {\n\t\t\t\tthis.$nav = this.$nav.filter(this.config.filter);\n\t\t\t}\n\n\t\t\t//Handle clicks on the nav\n\t\t\tthis.$nav.on('click.onePageNav', $.proxy(this.handleClick, this));\n\n\t\t\t//Get the section positions\n\t\t\tthis.getPositions();\n\n\t\t\t//Handle scroll changes\n\t\t\tthis.bindInterval();\n\n\t\t\t//Update the positions on resize too\n\t\t\tthis.$win.on('resize.onePageNav', $.proxy(this.getPositions, this));\n\n\t\t\treturn this;\n\t\t},\n\n\t\tadjustNav: function(self, $parent) {\n\t\t\tself.$elem.find('.' + self.config.currentClass).removeClass(self.config.currentClass);\n\t\t\t$parent.addClass(self.config.currentClass);\n\t\t},\n\n\t\tbindInterval: function() {\n\t\t\tvar self = this;\n\t\t\tvar docHeight;\n\n\t\t\tself.$win.on('scroll.onePageNav', function() {\n\t\t\t\tself.didScroll = true;\n\t\t\t});\n\n\t\t\tself.t = setInterval(function() {\n\t\t\t\tdocHeight = self.$doc.height();\n\n\t\t\t\t//If it was scrolled\n\t\t\t\tif(self.didScroll) {\n\t\t\t\t\tself.didScroll = false;\n\t\t\t\t\tself.scrollChange();\n\t\t\t\t}\n\n\t\t\t\t//If the document height changes\n\t\t\t\tif(docHeight !== self.docHeight) {\n\t\t\t\t\tself.docHeight = docHeight;\n\t\t\t\t\tself.getPositions();\n\t\t\t\t}\n\t\t\t}, 250);\n\t\t},\n\n\t\tgetHash: function($link) {\n\t\t\treturn $link.attr('href').split('#')[1];\n\t\t},\n\n\t\tgetPositions: function() {\n\t\t\tvar self = this;\n\t\t\tvar linkHref;\n\t\t\tvar topPos;\n\t\t\tvar $target;\n\n\t\t\tself.$nav.each(function() {\n\t\t\t\tlinkHref = self.getHash($(this));\n\t\t\t\t$target = $('#' + linkHref);\n\n\t\t\t\tif($target.length) {\n\t\t\t\t\ttopPos = $target.offset().top;\n\t\t\t\t\tself.sections[linkHref] = Math.round(topPos);\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\n\t\tgetSection: function(windowPos) {\n\t\t\tvar returnValue = null;\n\t\t\tvar windowHeight = Math.round(this.$win.height() * this.config.scrollThreshold);\n\n\t\t\tfor(var section in this.sections) {\n\t\t\t\tif((this.sections[section] - windowHeight) < windowPos) {\n\t\t\t\t\treturnValue = section;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn returnValue;\n\t\t},\n\n\t\thandleClick: function(e) {\n\t\t\tvar self = this;\n\t\t\tvar $link = $(e.currentTarget);\n\t\t\tvar $parent = $link.parent();\n\t\t\tvar newLoc = '#' + self.getHash($link);\n\n\t\t\tif(!$parent.hasClass(self.config.currentClass)) {\n\t\t\t\t//Start callback\n\t\t\t\tif(self.config.begin) {\n\t\t\t\t\tself.config.begin();\n\t\t\t\t}\n\n\t\t\t\t//Change the highlighted nav item\n\t\t\t\tself.adjustNav(self, $parent);\n\n\t\t\t\t//Removing the auto-adjust on scroll\n\t\t\t\tself.unbindInterval();\n\n\t\t\t\t//Scroll to the correct position\n\t\t\t\tself.scrollTo(newLoc, function() {\n\t\t\t\t\t//Do we need to change the hash?\n\t\t\t\t\tif(self.config.changeHash) {\n\t\t\t\t\t\twindow.location.hash = newLoc;\n\t\t\t\t\t}\n\n\t\t\t\t\t//Add the auto-adjust on scroll back in\n\t\t\t\t\tself.bindInterval();\n\n\t\t\t\t\t//End callback\n\t\t\t\t\tif(self.config.end) {\n\t\t\t\t\t\tself.config.end();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\te.preventDefault();\n\t\t},\n\n\t\tscrollChange: function() {\n\t\t\tvar windowTop = this.$win.scrollTop();\n\t\t\tvar position = this.getSection(windowTop);\n\t\t\tvar $parent;\n\n\t\t\t//If the position is set\n\t\t\tif(position !== null) {\n\t\t\t\t$parent = this.$elem.find('a[href$=\"#' + position + '\"]').parent();\n\n\t\t\t\t//If it's not already the current section\n\t\t\t\tif(!$parent.hasClass(this.config.currentClass)) {\n\t\t\t\t\t//Change the highlighted nav item\n\t\t\t\t\tthis.adjustNav(this, $parent);\n\n\t\t\t\t\t//If there is a scrollChange callback\n\t\t\t\t\tif(this.config.scrollChange) {\n\t\t\t\t\t\tthis.config.scrollChange($parent);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tscrollTo: function(target, callback) {\n\t\t\tvar offset = $(target).offset().top - this.config.padding;\n\n\t\t\t$('html, body').animate({\n\t\t\t\tscrollTop: offset\n\t\t\t}, this.config.scrollSpeed, this.config.easing, callback);\n\t\t},\n\n\t\tunbindInterval: function() {\n\t\t\tclearInterval(this.t);\n\t\t\tthis.$win.unbind('scroll.onePageNav');\n\t\t}\n\t};\n\n\tOnePageNav.defaults = OnePageNav.prototype.defaults;\n\n\t$.fn.onePageNav = function(options) {\n\t\treturn this.each(function() {\n\t\t\tnew OnePageNav(this, options).init();\n\t\t});\n\t};\n\n})( jQuery, window , document );"
  },
  {
    "path": "js/jquery.tagcloud.js",
    "content": "(function($) {\n\n  $.fn.tagcloud = function(options) {\n    var opts = $.extend({}, $.fn.tagcloud.defaults, options);\n    tagWeights = this.map(function(){\n      return $(this).attr(\"rel\");\n    });\n    tagWeights = jQuery.makeArray(tagWeights).sort(compareWeights);\n    lowest = tagWeights[0];\n    highest = tagWeights.pop();\n    range = highest - lowest;\n    if(range === 0) {range = 1;}\n    // Sizes\n    if (opts.size) {\n      fontIncr = (opts.size.end - opts.size.start)/range;\n    }\n    // Colors\n    if (opts.color) {\n      colorIncr = colorIncrement (opts.color, range);\n    }\n    return this.each(function() {\n      weighting = $(this).attr(\"rel\") - lowest;\n      if (opts.size) {\n        $(this).css({\"font-size\": opts.size.start + (weighting * fontIncr) + opts.size.unit});\n      }\n      if (opts.color) {\n        // change color to background-color\n        $(this).css({\"backgroundColor\": tagColor(opts.color, colorIncr, weighting)});\n      }\n    });\n  };\n\n  $.fn.tagcloud.defaults = {\n    size: {start: 14, end: 18, unit: \"pt\"}\n  };\n\n  // Converts hex to an RGB array\n  function toRGB (code) {\n    if (code.length == 4) {\n      code = jQuery.map(/\\w+/.exec(code), function(el) {return el + el; }).join(\"\");\n    }\n    hex = /(\\w{2})(\\w{2})(\\w{2})/.exec(code);\n    return [parseInt(hex[1], 16), parseInt(hex[2], 16), parseInt(hex[3], 16)];\n  }\n\n  // Converts an RGB array to hex\n  function toHex (ary) {\n    return \"#\" + jQuery.map(ary, function(i) {\n      hex =  i.toString(16);\n      hex = (hex.length == 1) ? \"0\" + hex : hex;\n      return hex;\n    }).join(\"\");\n  }\n\n  function colorIncrement (color, range) {\n    return jQuery.map(toRGB(color.end), function(n, i) {\n      return (n - toRGB(color.start)[i])/range;\n    });\n  }\n\n  function tagColor (color, increment, weighting) {\n    rgb = jQuery.map(toRGB(color.start), function(n, i) {\n      ref = Math.round(n + (increment[i] * weighting));\n      if (ref > 255) {\n        ref = 255;\n      } else {\n        if (ref < 0) {\n          ref = 0;\n        }\n      }\n      return ref;\n    });\n    return toHex(rgb);\n  }\n\n  function compareWeights(a, b)\n  {\n    return a - b;\n  }\n\n})(jQuery);\n"
  },
  {
    "path": "js/snackbar.js",
    "content": "/**\n * SnackBar.js\n * \n * This small component is borrowed from \n * https://codepen.io/wibblymat/pen/avAjq\n */\n\n\nvar createSnackbar = (function() {\n  // Any snackbar that is already shown\n  var previous = null;\n  \n/*\n<div class=\"paper-snackbar\">\n  <button class=\"action\">Dismiss</button>\n  This is a longer message that won't fit on one line. It is, inevitably, quite a boring thing. Hopefully it is still useful.\n</div>\n*/\n  \n  return function(config) {\n    var message = config.message,\n      actionText = config.actionText,\n      action = config.action,\n      duration = config.duration;\n\n    if (previous) {\n      previous.dismiss();\n    }\n    var snackbar = document.createElement('div');\n    snackbar.className = 'paper-snackbar';\n    snackbar.dismiss = function() {\n      this.style.opacity = 0;\n    };\n    var text = document.createTextNode(message);\n    snackbar.appendChild(text);\n    if (actionText) {\n      if (!action) {\n        action = snackbar.dismiss.bind(snackbar);\n      }\n      var actionButton = document.createElement('button');\n      actionButton.className = 'action';\n      actionButton.innerHTML = actionText;\n      actionButton.addEventListener('click', action);\n      snackbar.appendChild(actionButton);\n    }\n    setTimeout(function() {\n      if (previous === this) {\n        previous.dismiss();\n      }\n    }.bind(snackbar), duration || 5000);\n    \n    snackbar.addEventListener('transitionend', function(event, elapsed) {\n      if (event.propertyName === 'opacity' && this.style.opacity == 0) {\n        this.parentElement.removeChild(this);\n        if (previous === this) {\n          previous = null;\n        }\n      }\n    }.bind(snackbar));\n\n\n    \n    previous = snackbar;\n    document.body.appendChild(snackbar);\n    // In order for the animations to trigger, I have to force the original style to be computed, and then change it.\n    getComputedStyle(snackbar).bottom;\n    snackbar.style.bottom = '0px';\n    snackbar.style.opacity = 1;\n  };\n})();\n"
  },
  {
    "path": "js/sw-registration.js",
    "content": "/* ===========================================================\n * sw-registration.js\n * ===========================================================\n * Copyright 2016 @huxpro\n * Licensed under Apache 2.0\n * Register service worker.\n * ========================================================== */\n\n// SW Version Upgrade Ref: <https://youtu.be/Gb9uI67tqV0>\n\nfunction handleRegistration(registration){\n  console.log('Service Worker Registered. ', registration)\n  /**\n   * ServiceWorkerRegistration.onupdatefound\n   * The service worker registration's installing worker changes.\n   */\n  registration.onupdatefound = (e) => {\n    const installingWorker = registration.installing;\n    installingWorker.onstatechange = (e) => {\n      if (installingWorker.state !== 'installed') return;\n      if (navigator.serviceWorker.controller) {\n        console.log('SW is updated');\n      } else {\n        console.log('A Visit without previous SW');\n        createSnackbar({\n          message: 'App ready for offline use.',\n          duration: 3000\n        })\n      }\n    };\n  }\n}\n\nif(navigator.serviceWorker){\n  // For security reasons, a service worker can only control the pages\n  // that are in the same directory level or below it. That's why we put sw.js at ROOT level.\n  navigator.serviceWorker\n    .register('/sw.js')\n    .then((registration) => handleRegistration(registration))\n    .catch((error) => {console.log('ServiceWorker registration failed: ', error)})\n\n  // register message receiver\n  // https://dbwriteups.wordpress.com/2015/11/16/service-workers-part-3-communication-between-sw-and-pages/\n  navigator.serviceWorker.onmessage = (e) => {\n    console.log('SW: SW Broadcasting:', event);\n    const data = e.data\n    \n    if(data.command == \"UPDATE_FOUND\"){\n      console.log(\"UPDATE_FOUND_BY_SW\", data);\n      createSnackbar({\n        message: \"Content updated.\",\n        actionText:\"refresh\",\n        action: function(e){location.reload()}\n      })\n    }\n  }\n}\n"
  },
  {
    "path": "less/highlight.less",
    "content": "// Atom's One Dark theme for Jekyll\n// Credits: https://github.com/mgyongyosi/OneDarkJekyll/\n\n.highlight,\npre.highlight {\n  background: #282c34;\n  color: #abb2bf;\n}\n.highlight pre {\n  background: #282c34;\n}\n.highlight .hll {\n  background: #282c34;\n}\n.highlight .c {\n  color: #5c6370;\n  font-style: italic;\n}\n.highlight .err {\n  color: #960050;\n  background-color: #1e0010;\n}\n.highlight .k {\n  color: #c678dd;\n}\n.highlight .l {\n  color: #98c379;\n}\n.highlight .n {\n  color: #abb2bf;\n}\n.highlight .o {\n  color: #abb2bf;\n}\n.highlight .p {\n  color: #abb2bf;\n}\n.highlight .cm {\n  color: #5c6370;\n  font-style: italic;\n}\n.highlight .cp {\n  color: #5c6370;\n  font-style: italic;\n}\n.highlight .c1 {\n  color: #5c6370;\n  font-style: italic;\n}\n.highlight .cs {\n  color: #5c6370;\n  font-style: italic;\n}\n.highlight .ge {\n  font-style: italic;\n}\n.highlight .gs {\n  font-weight: 700;\n}\n.highlight .kc {\n  color: #c678dd;\n}\n.highlight .kd {\n  color: #c678dd;\n}\n.highlight .kn {\n  color: #c678dd;\n}\n.highlight .kp {\n  color: #c678dd;\n}\n.highlight .kr {\n  color: #c678dd;\n}\n.highlight .kt {\n  color: #c678dd;\n}\n.highlight .ld {\n  color: #98c379;\n}\n.highlight .m {\n  color: #d19a66;\n}\n.highlight .s {\n  color: #98c379;\n}\n.highlight .na {\n  color: #d19a66;\n}\n.highlight .nb {\n  color: #e5c07b;\n}\n.highlight .nc {\n  color: #e5c07b;\n}\n.highlight .no {\n  color: #e5c07b;\n}\n.highlight .nd {\n  color: #e5c07b;\n}\n.highlight .ni {\n  color: #e5c07b;\n}\n.highlight .ne {\n  color: #e5c07b;\n}\n.highlight .nf {\n  color: #abb2bf;\n}\n.highlight .nl {\n  color: #e5c07b;\n}\n.highlight .nn {\n  color: #abb2bf;\n}\n.highlight .nx {\n  color: #abb2bf;\n}\n.highlight .py {\n  color: #e5c07b;\n}\n.highlight .nt {\n  color: #e06c75;\n}\n.highlight .nv {\n  color: #e5c07b;\n}\n.highlight .ow {\n  font-weight: 700;\n}\n.highlight .w {\n  color: #f8f8f2;\n}\n.highlight .mf {\n  color: #d19a66;\n}\n.highlight .mh {\n  color: #d19a66;\n}\n.highlight .mi {\n  color: #d19a66;\n}\n.highlight .mo {\n  color: #d19a66;\n}\n.highlight .sb {\n  color: #98c379;\n}\n.highlight .sc {\n  color: #98c379;\n}\n.highlight .sd {\n  color: #98c379;\n}\n.highlight .s2 {\n  color: #98c379;\n}\n.highlight .se {\n  color: #98c379;\n}\n.highlight .sh {\n  color: #98c379;\n}\n.highlight .si {\n  color: #98c379;\n}\n.highlight .sx {\n  color: #98c379;\n}\n.highlight .sr {\n  color: #56b6c2;\n}\n.highlight .s1 {\n  color: #98c379;\n}\n.highlight .ss {\n  color: #56b6c2;\n}\n.highlight .bp {\n  color: #e5c07b;\n}\n.highlight .vc {\n  color: #e5c07b;\n}\n.highlight .vg {\n  color: #e5c07b;\n}\n.highlight .vi {\n  color: #e06c75;\n}\n.highlight .il {\n  color: #d19a66;\n}\n.highlight .gu {\n  color: #75715e;\n}\n.highlight .gd {\n  color: #f92672;\n}\n.highlight .gi {\n  color: #a6e22e;\n}\n\n// Customize for Hux Blog\n.highlighter-rouge .highlight {\n  margin-bottom: 10px;\n  border-radius: 6px;\n  pre {\n    font-size: 14px;\n    line-height: 1.5;\n    color: #555;\n    background: transparent;\n    border: 0;\n    margin: 0;\n    padding: 0;\n    word-wrap: normal; // Safari Bug\n  }\n  .rouge-code {\n    pre {\n      color: #abb2bf; // default color for nil-lang code\n    }\n  }\n  .table-responsive {\n    margin: 0px;\n    border: 0px;\n  }\n  .table {\n    margin: 0px;\n    table-layout: fixed;\n  }\n  table > tbody > tr {\n    > td {\n      margin: 0;\n      border: 0;\n      padding: 0;\n      > pre {\n        padding: 14px;\n      }\n    }\n  }\n  td.rouge-gutter {\n    width: 56px;\n  }\n  .lineno {\n    text-align: right;\n    // border-right: 1px solid #373434;\n    border-radius: 0px;\n  }\n}\n/* Landscape phones and down */\n@media (max-width: 480px) { \n  .highlighter-rouge .highlight {\n    margin-left: -15px;\n    margin-right: -15px;\n    border-radius: 0px;\n    td.rouge-gutter {\n      width: 46px;\n    }\n  }\n}"
  },
  {
    "path": "less/hux-blog.less",
    "content": "@import \"variables.less\";\n@import \"mixins.less\";\n@import \"sidebar.less\";\n@import \"side-catalog.less\";\n@import \"snackbar.less\";\n@import \"highlight.less\";\n\n// Global Components\n\nbody {\n  .sans-serif;\n  font-size: 16px;\n  // Hux mpdify to 16px (Mobile First), and increase to 20px while 768+ width\n  color: @gray-dark;\n  //-webkit-user-select:text; //对于 Blog 还是先不开启这句。\n  overflow-x: hidden;\n\n  text-rendering: auto;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\n// -- Typography\n\np {\n  margin: 30px 0;\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n  .sans-serif;\n  line-height: 1.1;\n  font-weight: bold;\n}\nh4 {\n  font-size: 21px;\n}\na {\n  color: @gray-dark;\n  &:hover,\n  &:focus {\n    color: @brand-primary;\n  }\n}\n\na img {\n  &:hover,\n  &:focus {\n    cursor: zoom-in;\n  }\n}\n\narticle {\n  overflow: hidden;\n}\nblockquote {\n  color: gray;\n  font-style: italic;\n  font-size: 0.95em;\n  margin: 20px 0 20px;\n  p {\n    margin: 0;\n  }\n}\n\n// Utils Style Class can be used in Markdown.\nsmall.img-hint {\n  display: block;\n  margin-top: -20px;\n  text-align: center;\n}\nbr + small.img-hint {\n  margin-top: -40px;\n}\nimg.shadow {\n  box-shadow: rgba(0, 0, 0, 0.258824) 0px 2px 5px 0px;\n}\n// Utils Style End\n\n// Select\nselect {\n  -webkit-appearance: none;\n  margin-top: 15px;\n  color: #337ab7;\n  border-color: #337ab7;\n  padding: 0em 0.4em;\n  background: white;\n  &.sel-lang {\n    min-height: 28px;\n    font-size: 14px;\n  }\n}\n\n// override table style in bootstrap\ntable.table > tbody, table.table > thead {\n  th,\n  td {\n    border: 1px solid #eee;\n  }\n}\n\nhr.small {\n  max-width: 100px;\n  margin: 15px auto;\n  border-width: 4px;\n  border-color: white;\n}\n// add by Hux\npre,\n.table-responsive {\n  // sometimes you should use vendor-feature.\n  -webkit-overflow-scrolling: touch;\n}\npre code {\n  display: block;\n  width: auto;\n  white-space: pre; // save it but no-wrap;\n  word-wrap: normal; // no-wrap\n}\ncode,\nkbd,\npre,\nsamp {\n  font-family: \"Fira Code\", Menlo, Monaco, Consolas, \"Courier New\", monospace;\n}\n\n// In the list of posts\n.postlist-container {\n  margin-bottom: 15px;\n}\n\n// In the post.\n.post-container {\n  a {\n    // display: inline-block;\n    // safari has bugs on word-break on inline elements.\n    color: #337ab7;\n    // different to @brand-primary\n    &:hover,\n    &:focus {\n      color: @brand-primary;\n    }\n  }\n  h1,\n  h2,\n  h3,\n  h4,\n  h5,\n  h6 {\n    margin: 30px 0 10px;\n    line-height: 1.4;\n  }\n  h5 {\n    font-size: 19px;\n    font-weight: 600;\n    color: gray;\n    & + p {\n      margin-top: 5px;\n    }\n  }\n  h6 {\n    font-size: 16px;\n    font-weight: 600;\n    color: gray;\n    & + p {\n      margin-top: 5px;\n    }\n  }\n  // downscale the font a little bit in mobile\n  @media screen and (max-width: 768px) {\n    h1 { font-size: 30px; }\n    h2 { font-size: 24px; }\n    h3 { font-size: 21px; }\n    h4 { font-size: 19px; }\n  }\n  ul,\n  ol {\n    margin-bottom: 40px;\n    @media screen and (max-width: 768px) {\n      & {\n        padding-left: 30px;\n      }\n    }\n    @media screen and (max-width: 500px) {\n      & {\n        padding-left: 20px;\n      }\n    }\n  }\n  ol ol,\n  ol ul,\n  ul ol,\n  ul ul {\n    margin-bottom: 5px;\n  }\n  li {\n    p {\n      margin: 0;\n      margin-bottom: 5px;\n    }\n    h1,\n    h2,\n    h3,\n    h4,\n    h5,\n    h6 {\n      line-height: 2;\n      margin-top: 20px;\n    }\n  }\n  // V1.6 Hux display title by default.\n  .pager li {\n    width: 48%;\n    &.next {\n      float: right;\n    }\n    &.previous {\n      float: left;\n    }\n    > a {\n      width: 100%;\n      > span {\n        color: @gray;\n        font-weight: normal;\n        letter-spacing: 0.5px;\n      }\n    }\n  }\n  .anchorjs-link {\n    // I can not understand this but this made anchor always in the same line as title\n    position: absolute; \n    padding-top: 1px;\n  }\n}\n\n// Navigation\n\n// materialize, mobile only\n@media only screen and (max-width: 767px) {\n  /**\n\t * Layout\n\t * Since V1.6 we use absolute positioning to prevent to expand container-fluid\n\t * which would cover tags. A absolute positioning make a new layer.\n\t */\n  .navbar-default .navbar-collapse {\n    position: absolute;\n    right: 0;\n    border: none;\n    background: white;\n    box-shadow: 0px 5px 10px 2px rgba(0, 0, 0, 0.2);\n    box-shadow: rgba(0, 0, 0, 0.117647) 0px 1px 6px,\n      rgba(0, 0, 0, 0.239216) 0px 1px 4px;\n    border-radius: 2px;\n    width: 170px;\n  }\n  /**\n\t * Animation\n\t * HuxBlog-Navbar using genuine Material Design Animation\n\t */\n  #huxblog_navbar {\n    /**\n\t\t * Sharable code and 'out' function\n\t\t */\n    // the container do width-transition\n    opacity: 0;\n    transform: scaleX(0);\n    transform-origin: top right;\n    transition: all 200ms cubic-bezier(0.47, 0, 0.4, 0.99) 0ms;\n    -webkit-transform: scaleX(0);\n    -webkit-transform-origin: top right;\n    -webkit-transition: all 200ms cubic-bezier(0.47, 0, 0.4, 0.99) 0ms;\n    a {\n      font-size: 13px;\n      line-height: 28px;\n    }\n    .navbar-collapse {\n      // the navbar do height-transition\n      height: 0px; // to solve container-mask-tags issue (default state).\n      transform: scaleY(0);\n      transform-origin: top right;\n      transition: transform 400ms cubic-bezier(0.32, 1, 0.23, 1) 0ms;\n      -webkit-transform: scaleY(0);\n      -webkit-transform-origin: top right;\n      -webkit-transition: -webkit-transform 400ms cubic-bezier(0.32, 1, 0.23, 1)\n        0ms;\n    }\n    li {\n      opacity: 0;\n      transition: opacity 100ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;\n      -webkit-transition: opacity 100ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;\n    }\n    /**\n\t\t *'In' Animation\n\t\t */\n    &.in {\n      transform: scaleX(1);\n      -webkit-transform: scaleX(1);\n      opacity: 1;\n      transition: all 250ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;\n      -webkit-transition: all 250ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;\n      .navbar-collapse {\n        transform: scaleY(1);\n        -webkit-transform: scaleY(1);\n        transition: transform 500ms cubic-bezier(0.23, 1, 0.32, 1);\n        -webkit-transition: -webkit-transform 500ms\n          cubic-bezier(0.23, 1, 0.32, 1);\n      }\n      li {\n        opacity: 1;\n        transition: opacity 450ms cubic-bezier(0.23, 1, 0.32, 1) 205ms;\n        -webkit-transition: opacity 450ms cubic-bezier(0.23, 1, 0.32, 1) 205ms;\n      }\n    }\n  }\n}\n\n.navbar-custom {\n  // materialize\n  background: none;\n  border: none;\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  z-index: 3;\n  .sans-serif;\n  .navbar-brand {\n    font-weight: 800;\n    // materialize\n    color: white;\n    height: 56px;\n    line-height: 25px;\n    &:hover {\n      color: rgba(255, 255, 255, 0.8);\n    }\n  }\n  .nav {\n    li {\n      a {\n        text-transform: uppercase;\n        font-size: 12px;\n        line-height: 20px;\n        font-weight: 800;\n        letter-spacing: 1px;\n        // only highlight in mobile\n        &:active {\n          background: rgba(0, 0, 0, 0.12);\n        }\n      }\n    }\n  }\n  @media only screen and (min-width: 768px) {\n    body {\n      font-size: 20px;\n    }\n    background: transparent;\n    border-bottom: 1px solid transparent;\n    .navbar-brand {\n      color: white;\n      padding: 20px;\n      line-height: 20px;\n      &:hover,\n      &:focus {\n        color: @white-faded;\n      }\n    }\n    .nav {\n      li {\n        a {\n          color: white;\n          padding: 20px;\n          &:hover,\n          &:focus {\n            color: @white-faded;\n          }\n          &:active {\n            background: none;\n          }\n        }\n      }\n    }\n  }\n  &.invert {\n    .navbar-toggle {\n      &:active {\n        background-color: rgba(0, 0, 0, 0.05);\n      }\n      .icon-bar{\n        background-color: @gray-dark;\n      }\n    }\n    .navbar-brand {\n      color: @gray-dark;\n      &:hover,\n      &:focus {\n        color: @brand-primary;\n      }\n    }\n    .nav {\n      li {\n        a {\n          color: @gray-dark;\n          &:hover,\n          &:focus {\n            color: @brand-primary;\n          }\n        }\n      }\n    }\n  }\n  @media only screen and (min-width: 1170px) {\n    -webkit-transition: background-color 0.3s;\n    -moz-transition: background-color 0.3s;\n    transition: background-color 0.3s;\n    /* Force Hardware Acceleration in WebKit */\n    -webkit-transform: translate3d(0, 0, 0);\n    -moz-transform: translate3d(0, 0, 0);\n    -ms-transform: translate3d(0, 0, 0);\n    -o-transform: translate3d(0, 0, 0);\n    transform: translate3d(0, 0, 0);\n    -webkit-backface-visibility: hidden;\n    backface-visibility: hidden;\n    &.is-fixed {\n      /* when the user scrolls down, we hide the header right above the viewport */\n      position: fixed;\n      top: -61px;\n      background-color: fade(white, 90%);\n      border-bottom: 1px solid darken(white, 5%);\n      -webkit-transition: -webkit-transform 0.3s;\n      -moz-transition: -moz-transform 0.3s;\n      transition: transform 0.3s;\n      .navbar-brand {\n        color: @gray-dark;\n        &:hover,\n        &:focus {\n          color: @brand-primary;\n        }\n      }\n      .nav {\n        li {\n          a {\n            color: @gray-dark;\n            &:hover,\n            &:focus {\n              color: @brand-primary;\n            }\n          }\n        }\n      }\n    }\n    &.is-visible {\n      /* if the user changes the scrolling direction, we show the header */\n      -webkit-transform: translate3d(0, 100%, 0);\n      -moz-transform: translate3d(0, 100%, 0);\n      -ms-transform: translate3d(0, 100%, 0);\n      -o-transform: translate3d(0, 100%, 0);\n      transform: translate3d(0, 100%, 0);\n    }\n  }\n}\n\n// Header\n\n.intro-header {\n  &.style-text {\n    background: none;\n    .site-heading,\n    .post-heading,\n    .page-heading {\n      padding: 85px 0 20px;\n      color: @gray-dark;\n      .subheading{\n        margin-bottom: 15px;\n      }\n    }\n    .tags {\n      a, .tag {\n        border-color: @gray-dark;\n        color: @gray-dark;\n        &:hover,\n        &:active {\n          background-color: rgba(0, 0, 0, 0.05);\n        }\n      }\n    }\n  }\n  background: no-repeat center center;\n  background-color: @gray;\n  background-attachment: scroll;\n  .background-cover;\n  // NOTE: Background images are set within the HTML using inline CSS!\n  margin-bottom: 0px; /* 0 on mobile, modify by Hux */\n  @media only screen and (min-width: 768px) {\n    margin-bottom: 20px; /* response on desktop */\n  }\n  .site-heading,\n  .post-heading,\n  .page-heading {\n    padding: 85px 0 55px;\n    color: white;\n    @media only screen and (min-width: 768px) {\n      padding: 150px 0;\n    }\n  }\n  // masterialize\n  .site-heading {\n    padding: 95px 0 70px;\n    @media only screen and (min-width: 768px) {\n      padding: 150px 0;\n    }\n  }\n  .site-heading,\n  .page-heading {\n    text-align: center;\n    h1 {\n      margin-top: 0;\n      font-size: 50px;\n    }\n    .subheading {\n      .sans-serif;\n      font-size: 18px;\n      line-height: 1.1;\n      display: block;\n      font-weight: 300;\n      margin: 10px 0 0;\n    }\n    @media only screen and (min-width: 768px) {\n      h1 {\n        font-size: 80px;\n      }\n    }\n  }\n  .post-heading {\n    h1 {\n      font-size: 30px;\n      margin-bottom: 24px;\n    }\n    .subheading,\n    .meta {\n      line-height: 1.1;\n      display: block;\n    }\n    .subheading {\n      .sans-serif;\n      font-size: 17px;\n      line-height: 1.4;\n      font-weight: normal;\n      margin: 10px 0 30px;\n      margin-top: -5px;\n    }\n    .meta {\n      .serif;\n      font-style: italic;\n      font-weight: 300;\n      font-size: 16px;\n      a {\n        color: white;\n      }\n    }\n    @media only screen and (min-width: 768px) {\n      h1 {\n        font-size: 55px;\n      }\n      .subheading {\n        font-size: 30px;\n      }\n      .meta {\n        font-size: 20px;\n      }\n    }\n  }\n  .header-img-credit {\n    position: absolute;\n    bottom: 6px;\n    right: 9px;\n    color: white;\n    opacity: 0.3;\n    font-size: 10px;\n    z-index: 1;\n    a {\n      color: white;\n    }\n    @media only screen and (min-width: 768px) {\n      & {\n        font-size: 12px;\n        bottom: 10px;\n        right: 15px;\n      }\n    }\n  }\n}\n\n// Post Preview Pages (Home Page)\n\n.post-preview {\n  > a {\n    color: @gray-dark;\n    &:hover,\n    &:focus {\n      text-decoration: none;\n      color: @brand-primary;\n    }\n    > .post-title {\n      font-size: 21px;\n      line-height: 1.3;\n      margin-top: 30px;\n      margin-bottom: 8px;\n    }\n    > .post-subtitle {\n      font-size: 15px;\n      line-height: 1.3;\n      margin: 0;\n      font-weight: 300;\n      margin-bottom: 10px;\n    }\n  }\n  > .post-meta {\n    .serif;\n    color: @gray;\n    font-size: 16px;\n    font-style: italic;\n    margin-top: 0;\n    > a {\n      text-decoration: none;\n      color: @gray-dark;\n      &:hover,\n      &:focus {\n        color: @brand-primary;\n        text-decoration: underline;\n      }\n    }\n  }\n  @media only screen and (min-width: 768px) {\n    > a {\n      > .post-title {\n        font-size: 26px;\n        line-height: 1.3;\n        margin-bottom: 10px;\n      }\n      > .post-subtitle {\n        font-size: 16px;\n      }\n    }\n    .post-meta {\n      font-size: 18px;\n    }\n  }\n}\n\n// Hux add home-page post-content-preview\n.post-content-preview {\n  font-size: 13px;\n  font-style: italic;\n  color: #a3a3a3;\n  &:hover {\n    color: @brand-primary;\n  }\n  @media only screen and (min-width: 768px) {\n    font-size: 14px;\n  }\n}\n\n// Sections\n\n.section-heading {\n  font-size: 36px;\n  margin-top: 60px;\n  font-weight: 700;\n}\n\n.caption {\n  text-align: center;\n  font-size: 14px;\n  padding: 10px;\n  font-style: italic;\n  margin: 0;\n  display: block;\n  border-bottom-right-radius: 5px;\n  border-bottom-left-radius: 5px;\n}\n\nfooter {\n  font-size: 20px;\n  padding: 50px 0 65px;\n  .list-inline {\n    margin: 0;\n    padding: 0;\n  }\n  .copyright {\n    font-size: 14px;\n    text-align: center;\n    margin-bottom: 0;\n    a {\n      color: #337ab7;\n      // different to @brand-primary\n      &:hover,\n      &:focus {\n        color: @brand-primary;\n      }\n    }\n  }\n}\n\n// Contact Form Styles\n\n.floating-label-form-group {\n  font-size: 14px;\n  position: relative;\n  margin-bottom: 0;\n  padding-bottom: 0.5em;\n  border-bottom: 1px solid @gray-light;\n  input,\n  textarea {\n    z-index: 1;\n    position: relative;\n    padding-right: 0;\n    padding-left: 0;\n    border: none;\n    border-radius: 0;\n    font-size: 1.5em;\n    background: none;\n    box-shadow: none !important;\n    resize: none;\n  }\n  label {\n    display: block;\n    z-index: 0;\n    position: relative;\n    top: 2em;\n    margin: 0;\n    font-size: 0.85em;\n    line-height: 1.764705882em;\n    vertical-align: middle;\n    vertical-align: baseline;\n    opacity: 0;\n    -webkit-transition: top 0.3s ease, opacity 0.3s ease;\n    -moz-transition: top 0.3s ease, opacity 0.3s ease;\n    -ms-transition: top 0.3s ease, opacity 0.3s ease;\n    transition: top 0.3s ease, opacity 0.3s ease;\n  }\n  &::not(:first-child) {\n    padding-left: 14px;\n    border-left: 1px solid @gray-light;\n  }\n}\n\n.floating-label-form-group-with-value {\n  label {\n    top: 0;\n    opacity: 1;\n  }\n}\n\n.floating-label-form-group-with-focus {\n  label {\n    color: @brand-primary;\n  }\n}\n\nform .row:first-child .floating-label-form-group {\n  border-top: 1px solid @gray-light;\n}\n\n// Button Styles\n\n.btn {\n  .sans-serif;\n  text-transform: uppercase;\n  font-size: 14px;\n  font-weight: 800;\n  letter-spacing: 1px;\n  border-radius: 0;\n  padding: 15px 25px;\n}\n\n.btn-lg {\n  font-size: 16px;\n  padding: 25px 35px;\n}\n\n.btn-default {\n  &:hover,\n  &:focus {\n    background-color: @brand-primary;\n    border: 1px solid @brand-primary;\n    color: white;\n  }\n}\n\n// Pager Styling\n\n.pager {\n  margin: 20px 0 0 !important;\n  padding: 0px !important;\n\n  li {\n    > a,\n    > span {\n      .sans-serif;\n      text-transform: uppercase;\n      font-size: 13px;\n      font-weight: 800;\n      letter-spacing: 1px;\n      padding: 10px;\n      background-color: white;\n      border-radius: 0;\n      @media only screen and (min-width: 768px) {\n        font-size: 14px;\n        padding: 15px 25px;\n      }\n    }\n\n    > a {\n      color: @gray-dark;\n    }\n    > a:hover,\n    > a:focus {\n      color: white;\n      background-color: @brand-primary;\n      border: 1px solid @brand-primary;\n\n      // V1.6 display title\n      > span {\n        color: white;\n      }\n    }\n  }\n\n  .disabled {\n    > a,\n    > a:hover,\n    > a:focus,\n    > span {\n      color: @gray;\n      background-color: @gray-dark;\n      cursor: not-allowed;\n    }\n  }\n}\n\n// -- Highlight Color Customization\n\n::-moz-selection {\n  color: white;\n  text-shadow: none;\n  background: @brand-primary;\n}\n\n::selection {\n  color: white;\n  text-shadow: none;\n  background: @brand-primary;\n}\n\nimg::selection {\n  color: white;\n  background: transparent;\n}\n\nimg::-moz-selection {\n  color: white;\n  background: transparent;\n}\n\n/* Hux add tags support */\n.d-none {\n  display: none !important;\n}\n.tags {\n  margin-bottom: -5px;\n  &.tags-sup {\n    a, .tag {\n      padding: 0 10px 0 12px;\n    }\n  }\n  a, .tag {\n    display: inline-block;\n    border: 1px solid rgba(255, 255, 255, 0.8);\n    border-radius: 999em;\n    padding: 0 10px 0 10px;\n    color: rgba(255, 255, 255, 1);\n    line-height: 24px;\n    font-size: 12px;\n    text-decoration: none;\n    margin: 0 1px;\n    margin-bottom: 6px;\n    cursor: pointer;\n    > sup {\n      margin-left: -2px;\n      font-weight: 700;\n    }\n    &:hover,\n    &:active {\n      color: white;\n      border-color: white;\n      background-color: rgba(255, 255, 255, 0.4);\n      text-decoration: none;\n    }\n    @media only screen and (min-width: 768px) {\n      margin-right: 5px;\n    }\n  }\n}\n\n#tag-heading {\n  padding: 70px 0 60px;\n  @media only screen and (min-width: 768px) {\n    padding: 55px 0;\n  }\n}\n// tag_cloud\n#tag_cloud {\n  margin: 20px 0 15px 0;\n  a,\n  .tag {\n    transition-property: all;\n    transition-duration: 0.4s;\n    transition-timing-function: ease;\n    font-size: 14px;\n    border: none;\n    line-height: 28px;\n    margin: 0 2px;\n    margin-bottom: 8px;\n    background: #f3f5f5;\n    &:hover,\n    &:active,\n    &.focus {\n      background-color: #0085a1 !important;\n    }\n    &.focus{\n      box-shadow: rgba(0,0,0,.117647) 0 1px 6px, rgba(0,0,0,.239216) 0 1px 4px;\n    }\n    &.tag-button--all {\n      font-weight: 700;\n      color: #0085a1!important;\n      &:hover, &:active, &.focus{\n        background-color: #e4e4e4 !important;\n      }\n    }\n  }\n  @media only screen and (min-width: 768px) {\n    margin-bottom: 25px;\n  }\n}\n\n.tag-comments {\n  font-size: 12px;\n  @media only screen and (min-width: 768px) {\n    font-size: 14px;\n  }\n}\n\n.t {\n  //margin-top: 50px;\n  &:first-child {\n    margin-top: 0px;\n  }\n  hr:last-of-type {\n    //display: none;\n  }\n}\n.listing-seperator {\n  color: #0085a1;\n  font-size: 21px !important;\n  &::before {\n    margin-right: 5px;\n  }\n  @media only screen and (min-width: 768px) {\n    font-size: 20px !important;\n    line-height: 2 !important;\n  }\n}\n\n// Customize Style for Posts in Mini-Post-List\n.mini-post-list {\n  margin: 20px 0 15px 0;\n  .tag-text {\n    font-weight: 200;\n    .sans-serif;\n  }\n  .post-preview {\n    position: relative;\n    > a {\n      .post-title {\n        font-size: 16px;\n        font-weight: 500;\n        margin-top: 20px;\n      }\n      .post-subtitle {\n        font-size: 13px;\n      }\n    }\n    > .post-meta {\n      position: absolute;\n      right: 5px;\n      bottom: 0px;\n      margin: 0px;\n      font-size: 12px;\n      line-height: 12px;\n    }\n    @media only screen and (min-width: 768px) {\n      margin-left: 20px;\n      > a {\n        > .post-title {\n          font-size: 18px;\n          line-height: 1.3;\n        }\n        > .post-subtitle {\n          font-size: 14px;\n        }\n      }\n      .post-meta {\n        font-size: 18px;\n      }\n    }\n  }\n}\n\n/* Tags support End*/\n\n/* Hux make all img responsible in post-container */\n.post-container img {\n  display: block;\n  max-width: 100%;\n  height: auto;\n  margin: 1.5em auto 1.6em auto;\n}\n\n/* Hux Optimize UserExperience */\n.navbar-default .navbar-toggle {\n  &:focus,\n  &:hover {\n    background-color: inherit;\n  }\n  &:active {\n    background-color: rgba(255, 255, 255, 0.25);\n  }\n}\n\n\n/* Hux customize Style for navBar button */\n\n.navbar-default .navbar-toggle {\n  border-color: transparent;\n  padding: 19px 16px;\n  margin-top: 2px;\n  margin-right: 2px;\n  margin-bottom: 2px;\n  border-radius: 50%;\n  .icon-bar {\n    width: 18px;\n    border-radius: 0px;\n    // materialize\n    background-color: white;\n  }\n  .icon-bar + .icon-bar {\n    margin-top: 3px;\n  }\n}\n\n/* Hux customize Style for Duoshuo */\n.comment {\n  margin-top: 20px;\n  #ds-thread {\n    #ds-reset {\n      a.ds-like-thread-button {\n        border: 1px solid #ddd;\n        border-radius: 0px;\n        background: white;\n        box-shadow: none;\n        text-shadow: none;\n      }\n      a.ds-thread-liked {\n      }\n      li.ds-tab a.ds-current {\n        border: 1px solid #ddd;\n        border-radius: 0px;\n        background: white;\n        box-shadow: none;\n        text-shadow: none;\n      }\n      .ds-textarea-wrapper {\n        //border:1px solid #ddd;\n        background: none;\n      }\n      .ds-gradient-bg {\n        background: none;\n      }\n      .ds-post-options {\n        border-bottom: 1px solid #ccc;\n      }\n      .ds-post-button {\n        border-bottom: 1px solid #ccc;\n      }\n      //v1.6 flatten button\n      .ds-post-button {\n        background: white;\n        box-shadow: none;\n        &:hover {\n          background: @gray-light;\n        }\n      }\n    }\n  }\n}\n#ds-smilies-tooltip ul.ds-smilies-tabs li a {\n  background: white !important;\n}\n\n// Hux fullscreen mode in 404.html\n\n.page-fullscreen .intro-header {\n  position: fixed;\n  left: 0;\n  top: 0;\n  width: 100%;\n  height: 100%;\n}\n.page-fullscreen #tag-heading {\n  position: fixed;\n  left: 0;\n  top: 0;\n  padding-bottom: 150px;\n  width: 100%;\n  height: 100%;\n  display: -webkit-box;\n  -webkit-box-orient: vertical;\n  -webkit-box-pack: center;\n  -webkit-box-align: center;\n\n  display: -webkit-flex;\n  -webkit-align-items: center;\n  -webkit-justify-content: center;\n  -webkit-flex-direction: column;\n\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  flex-direction: column;\n}\n.page-fullscreen footer {\n  position: absolute;\n  width: 100%;\n  bottom: 0;\n  padding-bottom: 20px;\n  opacity: 0.6;\n  color: #fff;\n}\n.page-fullscreen footer .copyright {\n  color: #fff;\n}\n.page-fullscreen footer .copyright a {\n  color: #fff;\n}\n.page-fullscreen footer .copyright a:hover {\n  color: #ddd;\n}\n\n// MathJax Overflow\n.MathJax_SVG_Display{\n  overflow: auto;\n  -webkit-overflow-scrolling: touch;\n}"
  },
  {
    "path": "less/mixins.less",
    "content": "// Mixins\n\n.transition-all() {\n    -webkit-transition: all 0.5s;\n    -moz-transition: all 0.5s;\n    transition: all 0.5s;\n}\n\n.background-cover() {\n    -webkit-background-size: cover;\n    -moz-background-size: cover;\n    background-size: cover;\n    -o-background-size: cover;\n}\n\n.serif() {\n\tfont-family: 'Lora', 'Times New Roman', serif;\n}\n\n.sans-serif () {\n\t/* Hux learn from\n     *     TypeIsBeautiful,\n     *     [This Post](http://zhuanlan.zhihu.com/ibuick/20186806) etc.\n     */\n\tfont-family:\n        // System Font            // https://www.webkit.org/blog/3709/using-the-system-font-in-web-content/\n        -apple-system,            // OSX ^10.11 & iOS ^9  San Francisco & 苹方 for Safari\n        BlinkMacSystemFont,       // OSX ^10.11 & iOS ^9  San Francisco & 苹方 for Blink \n\n        // English First\n        \"Helvetica Neue\",         // OSX\n        \"Arial\",                  // Win \"Helvetica\"\n        //\" Segoe UI\",            // Win ^8\n\n        // Chinese Fallback\n        \"PingFang SC\",            // OSX ^10.11 & iOS ^9  苹方（华康信凭黑）\n        \"Hiragino Sans GB\",       // OSX ^10.6            冬青黑体\n        \"STHeiti\",                // OSX <10.6  & iOS <9  华文黑体\n        \"Microsoft YaHei\",        // Win                  微软雅黑\n        \"Microsoft JhengHei\",     // Win                  微软正黑\n        \"Source Han Sans SC\",     // SourceHan - begin    思源黑体\n        \"Noto Sans CJK SC\",\n        \"Source Han Sans CN\",\n        \"Noto Sans SC\",\n        \"Source Han Sans TC\",\n        \"Noto Sans CJK TC\",       // SourceHan - end\n        \"WenQuanYi Micro Hei\",    // Linux                文泉驿微米黑\n        SimSun,                   // Win old              中易宋体\n        sans-serif;               // System Fallback\n\n\tline-height: 1.7;\n}\n"
  },
  {
    "path": "less/side-catalog.less",
    "content": "// Directory Section\n\n.catalog-container{\n    padding: 0px;\n}\n\n.side-catalog {\n    &.fixed{\n        position: fixed;\n        top: -21px;\n    }\n    &.fold{\n        .catalog-toggle::before{\n            content: \"+\";\n        }\n        .catalog-body{\n            display: none;\n        }\n    }\n    .catalog-toggle::before{\n        content: \"−\";\n        position: relative;\n        margin-right: 5px;\n        bottom: 1px;\n    }\n    display: block;\n    overflow: auto;\n    height: 100%;\n    padding-bottom: 40px;\n    width: 195px;\n    .catalog-body{\n        position: relative;\n        list-style: none;\n        height: auto;\n        overflow: hidden;\n        padding-left: 0px;\n        padding-right: 5px;\n        text-indent: 0;\n        li{\n            position: relative;\n            list-style: none;\n            a{\n                padding-left: 10px;\n                max-width: 180px;\n                display: inline-block;\n                vertical-align: middle;\n                height: 30px;\n                line-height: 30px;\n                overflow: hidden;\n                text-decoration: none;\n                white-space: nowrap;\n                text-overflow: ellipsis;\n            }\n        }\n        .h1_nav,.h2_nav,.h3_nav{\n            margin-left: 0;\n            font-size: 13px;\n            font-weight: bold;\n        }\n        .h4_nav,.h5_nav,.h6_nav{\n            margin-left: 10px;\n            font-size: 12px;\n            a{\n                max-width: 170px;\n            }\n        }\n        .active{\n            a{\n                color: #0085a1!important;\n            }\n            border-radius: 4px;\n            background-color: #F5F5F5;\n        }\n    }\n}\n\n@media (max-width: 1200px){\n    .side-catalog{\n        display: none;\n    }\n}"
  },
  {
    "path": "less/sidebar.less",
    "content": "@import \"variables.less\";\n\n// Sidebar Components\n\n// Large Screen\n@media (min-width: 1200px){\n    .post-container, .sidebar-container{\n        padding-right: 5%;\n    }\n}\n@media (min-width: 768px){\n    .post-container{\n        padding-right: 5%;\n    }\n}\n\n// Container of Sidebar, also Friends\n.sidebar-container{\n    color: @gray-l;\n    font-size: 14px;\n    h5{\n        color: @gray;\n        padding-bottom: 1em;\n        a{\n            color: @gray !important;\n            text-decoration: none;\n        }\n    }\n    a{\n        color: @gray-l !important;\n        &:hover, &:active{\n            color: @brand-primary !important;\n        }\n    }\n    .tags{\n        a{\n            border-color: @gray-l;\n            &:hover, &:active{\n                border-color: @brand-primary;\n            }\n        }\n    }\n    .short-about{\n        img{\n            width: 80%;\n            display: block;\n            border-radius: 5px;\n            margin-bottom: 20px;\n        }\n        p{\n            margin-top: 0px;\n            margin-bottom: 20px;\n        }\n        .list-inline>li{\n            padding-left: 0px;\n        }\n    }\n}\n"
  },
  {
    "path": "less/snackbar.less",
    "content": "/*\n  Please note this CSS is currently in   prototype form. We'll implement a cleaned up version in Web Starter Kit.\n*/\n\n.paper-snackbar {\n  transition-property: opacity, bottom, left, right, width, margin, border-radius;\n  transition-duration: 0.5s;\n  transition-timing-function: ease;\n  /*font-family: RobotoDraft;*/\n  font-size: 14px;\n  min-height: 14px;\n  background-color: #323232;\n  background-color: #0085a1;\n  position: fixed;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  color: white;\n  line-height: 22px;\n  padding: 18px 24px;\n  bottom: 0px;\n  opacity: 0;\n\n}\n\n@media (min-width: 640px) {\n  .paper-snackbar {\n    /*\n    Desktop:\n      Single-line snackbar height: 48 dp tall\n      Minimum width: 288 dp\n      Maximum width: 568 dp\n      2 dp rounded corner\n      Text: Roboto Regular 14 sp\n      Action button: Roboto Medium 14 sp, all-caps text\n      Default background fill: #323232 100%\n    */\n\n    min-width: 288px;\n    max-width: 568px;\n    display: inline-flex;\n    border-radius: 2px;\n    margin: 24px;\n    bottom: -100px;\n    \n  }\n}\n\n@media (max-width: 640px) {\n  .paper-snackbar {\n  /*\n  Mobile:\n    Single-line snackbar height: 48 dp\n    Multi-line snackbar height: 80 dp\n    Text: Roboto Regular 14 sp\n    Action button: Roboto Medium 14 sp, all-caps text\n    Default background fill: #323232 100%  \n  */\n    left: 0px;\n    right: 0px;\n  }\n}\n\n.paper-snackbar .action {\n  background: inherit;\n  display: inline-block;\n  border: none;\n  font-size: inherit;\n  text-transform: uppercase;\n  color: #ffeb3b;\n  margin: 0px 0px 0px 24px;\n  padding: 0px;\n  min-width: min-content;\n  outline: 0px;\n}\n\n\n\n\n/* Everything from here down is actually just for the demo - the material buttons and card, and various other pieces of styling */\n\n/* Variables */\n\n@blue: #4285f4;\n@white: #fff;\n@black: #000;\n@gray: #ccc;\n@button-width: 160px;\n\n@top: 1000;\n\n/* Buttons */\n.paper-button {\n    position: relative;\n    padding: 4px 0;\n    margin: 1em;\n    width: @button-width;\n    overflow: hidden;\n    user-select: none;\n    color: @black;\n    text-transform: uppercase;\n    border-radius: 3px;\n    outline-color: @gray;\n  \n    &:hover {\n        cursor: pointer;\n    }\n\n    // we don't leverage ripple\n    // .ripple {\n    //     position: absolute;\n    //     width: @button-width * 2;\n    //     height: @button-width * 2;\n    //     //background-color: rgba(@gray, 0.5);\n    //     left: 0;\n    //     top: 0;\n    //     border-radius: 50%;\n    //     margin-left: - @button-width;\n    //     margin-top: - @button-width;\n    //     transform: scaleX(0) scaleY(0);\n    //     z-index: 9000;\n    // }\n}\n\n.paper-button button, .paper-button input[type=\"submit\"] {\n  background: inherit;\n  border: none;\n  display: block;\n  width: 100%;\n  height: 100%;\n  /*font-family: 'Roboto';*/\n  font-size: 1em;\n  color: @black;\n  text-transform: uppercase;\n}\n\n.paper-button.colored, .paper-button.colored button {\n  color: @blue;\n}\n\n// .paper-button .ripple {\n//   background-color: rgba(@gray, 0.5);\n// }\n\n// .paper-button.raised-button.colored .ripple {\n//   background-color: rgba(@white, 0.5);\n// }\n\n// .paper-button.raised-button .ripple {\n//   background-color: rgba(@gray, 0.5);\n// }\n\n.paper-button.raised-button.colored {\n   background-color: @blue;\n}\n\n.paper-button .raised-button{\n  box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26);\n}\n\n.paper-button.raised-button.colored{\n  background: #4285f4;\n  color: #fff;\n}\n\n.paper-button[disabled] {\n    background-color: #EAEAEA !important;\n    color: #A8A8A8 !important;\n    cursor: auto;\n}\n\n.paper-button:hover{\n  background-color: #EAEAEA;\n}\n.paper-button.raised-button.colored:hover{\n  background-color: #3367d6;\n}\n\nbutton.paper-button {\n  border:0;\n  /*font-family: RobotoDraft, 'Helvetica Neue', Helvetica, Arial;*/\n  font-size: 1em;\n  line-height: 25px;\n  background-color: @white;\n}\n\n.paper-button input[type=\"submit\"] {\n  outline-color: @gray;  \n}\n\n.paper-button.colored.raised-button input[type=\"submit\"] {\n  color: @white;\n} \n\n/** Shadows **/\n.paper-shadow-animated.paper-shadow {\n  transition: box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.paper-shadow-top-z-1 {\n  box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.16);\n}\n\n.paper-shadow-bottom-z-1 {\n  box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26);\n}\n\n.paper-shadow-top-z-2 {\n  box-shadow: 0 6px 20px 0 rgba(0, 0, 0, 0.19);\n}\n\n.paper-shadow-bottom-z-2 {\n  box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2);\n}\n\n.paper-shadow-top-z-3 {\n  box-shadow: 0 17px 50px 0 rgba(0, 0, 0, 0.19);\n}\n\n.paper-shadow-bottom-z-3 {\n  box-shadow: 0 12px 15px 0 rgba(0, 0, 0, 0.24);\n}\n\n.paper-shadow-top-z-4 {\n  box-shadow: 0 25px 55px 0 rgba(0, 0, 0, 0.21);\n}\n\n\n\n/** Card **/\n.card {\n background: white;\n  width: 300px;\n  height: 300px;\n  position: relative;\n  margin: 16px;\n  border-radius: 2px; \n}\n\n\n\n"
  },
  {
    "path": "less/variables.less",
    "content": "// Variables\n\n@brand-primary: #0085A1;\n@gray-dark: lighten(black, 25%);\n@gray: gray;\n@gray-l: lighten(black, 75%);\n@white-faded: fade(white, 80%);\n@gray-light: #eee;\n"
  },
  {
    "path": "my/books/README.md",
    "content": "# 书籍出版\n\n\n\n### [《Spring Boot 编程思想》](https://mercyblitz.github.io/books/thinking-in-spring-boot/)\n- [《核心篇》](https://mercyblitz.github.io/books/thinking-in-spring-boot/core/preface/)（[京东](https://item.jd.com/12570242.html)、[当当](http://product.dangdang.com/26922557.html)、[天猫](https://detail.tmall.com/item.htm?id=589445956796) 热售中…）\n- 《运维篇》（即将完稿…)\n- 《Web 篇》(编写中…)\n\n"
  },
  {
    "path": "my/lessons/README.md",
    "content": "# 线上课程\n\n\n\n### 公开课\n\n- 「[B 站](https://space.bilibili.com/327910845)」\n  - [「小马哥 Java 分布式架构训练营」公开课](https://space.bilibili.com/327910845/channel/collectiondetail?sid=723397)\n  - [「小马哥技术周报」系列](https://space.bilibili.com/327910845/channel/detail?cid=52311)\n  - [「小马哥公开课」系列](https://space.bilibili.com/327910845/channel/detail?cid=78194)\n  - [「小马哥每日一问」系列](https://space.bilibili.com/327910845/channel/detail?cid=80313)\n  - [「小马哥慕课直播」系列](https://space.bilibili.com/327910845/channel/detail?cid=78193)\n  - [「小马哥极客直播」系列](https://space.bilibili.com/327910845/channel/detail?cid=120424)\n- 「[慕课网](https://www.imooc.com/u/5387391)」\n  - [Spring Boot 2.0深度实践-初遇Spring Boot](https://www.imooc.com/learn/933)\n  - [Spring Boot 2.0深度实践之系列总览](https://www.imooc.com/learn/1058)\n- 「[思否（SegementFault）](https://segmentfault.com/u/mercyblitz)」\n  - [「小马哥 2019 跨年直播」](https://mp.weixin.qq.com/s?__biz=MzIxNDU4NjE1OQ==&mid=2247484085&idx=1&sn=5905f53e69bae9d48b3783a83bde40b3)\n  - [「小马哥 2020 跨年直播」](https://ke.segmentfault.com/course/1650000021471857)\n  - [「小马哥 2021 跨年直播」](https://ke.segmentfault.com/course/1650000038776105)\n  - 「小马哥 2022 跨年直播」\n  - [「小马哥 2023 跨年直播」](https://ke.segmentfault.com/course/1650000043206740)\n\n\n\n### VIP 课\n\n- 「[小马哥 Java 分布式架构训练营](https://apprnzlvz344455.h5.xiaoeknow.com)」\n  - [第一期 Java 分布式架构 - 服务治理](https://mqu.xet.tech/s/1I2W75)\n  - [第二期 Java 分布式架构 - 模式、设计与实现](https://mqu.xet.tech/s/1UDiMh)\n  - 第三期 分布式高可用架构（2023年6月开课）\n  - 第四期 异地多活架构（2023年9月开课）\n\n- 「[极客时间](https://horde.geekbang.org/usercenter/9E0C7EB9B0F212)」\n  - [小马哥的 Java 项目实战营](https://u.geekbang.org/subject/java2nd)\n  - [小马哥讲Spring核心编程思想](https://time.geekbang.org/course/intro/100042601)\n  - [小马哥讲Spring AOP编程思想](https://time.geekbang.org/course/intro/100066301)\n\n- 「[慕课网](https://www.imooc.com/u/5387391)」\n  - [Spring Boot2.0深度实践之核心技术篇](http://t.cn/ReChCU9)\n\n- 「[思否（SegementFault）](https://segmentfault.com/u/mercyblitz)」\n  - [Java 微服务实践 - Spring Boot / Spring Cloud](https://segmentfault.com/ls/1650000011387052)（原价：~~1024~~，福利价（五折）：`512`）\n- [「一入 Java 深似海 」系列课程](http://t.cn/AisU8cBk)（原价：~~1024~~，福利价（五折）：`512`）"
  },
  {
    "path": "my/open-sources/README.md",
    "content": "# 开源项目\n\n\n\nGithub：[https://github.com/mercyblitz](https://github.com/mercyblitz)\n\n\n\n- Spring Cloud\n  - [Spring Cloud Alibaba](https://github.com/spring-cloud-incubator/spring-cloud-alibaba)\n- Apache\n  - [Apache Dubbo](https://github.com/apache/incubator-dubbo)\n  - [Apache Dubbo Spring Boot](https://github.com/apache/incubator-dubbo-spring-boot-project) \n- Alibaba\n  - [Alibaba Nacos](https://github.com/alibaba/nacos)\n    - [Nacos Spring](https://github.com/nacos-group/nacos-spring-project)\n    - [Nacos Spring Boot](https://github.com/nacos-group/nacos-spring-boot-project)\n  - [Alibaba Sentinel](https://github.com/alibaba/Sentinel)\n  - [Alibaba Velocity Spring Boot Project](https://github.com/alibaba/velocity-spring-boot-project)\n  - [Alibaba Spring Velocity Support](https://github.com/alibaba/spring-velocity-support/)\n  - [Alibaba Spring Context Support](https://github.com/alibaba/spring-context-support)\n  - [Alibaba Spring WebMVC Support](https://github.com/alibaba/spring-webmvc-support)\n  - [Alibaba Spring Boot Web Support](https://github.com/alibaba/spring-boot-web-support)\n\n"
  },
  {
    "path": "my/presentations/README.md",
    "content": "# 报告分享\n\n\n\n### 线上分享\n\n- 2020\n  - [05.28 Apache Dubbo 服务自省架构设计与实现](https://www.bilibili.com/video/BV1wQ4y1P7TM)\n  - [05.14 - Spring Cloud Alibaba - 重新定义 Java Cloud-Native](https://www.bilibili.com/video/BV1YQ4y1A7mG)\n\n\n\n### 线下分享\n\n- 2019\n  - 11.03 Dubbo Cloud Native 过去、现在与未来 · [2019 中国开源年会](https://my.oschina.net/u/3874284/blog/3124030)\n  - 09.17 How to involve in Dubbo · 华东师范大学（上海）\n  - 08.17 Dubbo Roadmap 2019 · [Dubbo 开发者日上海站](https://www.huodongxing.com/event/map/5502527446911)\n  - 07.22 What‘s new in Dubbo 2.7.4  · [Dubbo 开发者日深圳站](https://www.huodongxing.com/event/5498736958000)\n  - 06.22 Dubbo Spring Cloud 重塑微服务治理 · [GIAC 全球互联网架构大会 2019 深圳](http://giac.msup.com.cn/Giac/schedule/course?id=13979)\n  - 05.26 Apache Dubbo 服务自省与设计 · [Dubbo 开发者日北京站](https://www.huodongxing.com/event/8491826604100)\n  - 04.19 我的学思历程 · [电子工业出版社-博文视点](https://www.itdks.com/Course/detail?id=116284)\n  - 04.13 Dubbo Spring Cloud + Nacos 云原生开发 ·  [Nacos 技术沙龙杭州站](https://www.huodongxing.com/event/2485774991600)\n  - 03.23 Dubbo Spring Cloud 是什么？ · [Apache Dubbo 开发者沙龙 南京站](https://www.huodongxing.com/event/7482607661100)\n- 2018\n  - 12.01 Dubbo + Nacos 服务治理重新实现 · [Apache Dubbo 开发者沙龙 杭州站](https://www.huodongxing.com/event/3467333809600)\n  - 10.26 Spring Cloud Alibaba 致力于提供分布式应用开发的一站式解决方法 · [CNCC 2018](http://cncc2018.ccf.org.cn/cms/show.action?code=publish_ff80808162f165f90163070bf87105de&siteid=100000&channelid=0000000002)\n  - 06.23 Dubbo Cloud Native 之路的实践与思考 · [Dubbo开发者沙龙（上海站）- 2018 Aliware技术行](https://www.itdks.com/eventlist/detail/2307)\n- 2017 \n  - Dubbo 的过去、现在以及未来 · [GIAC 全球互联网架构大会 2017（上海）](http://2017.thegiac.com/)\n- 2016\n  - 微服务实践之路（二） · [2016 SegmentFault 开发者大会（杭州站）](https://segmentfault.com/sfdc-2016/hz)\n  - 微服务实践之路（一） · [2016 厦门互联网技术峰会](https://www.bagevent.com/event/227489)"
  },
  {
    "path": "my/training/README.md",
    "content": "# 培训\n\n\n\n\n\n## 课程大纲\n\n\n\n\n\n## 合作企业\n\n\n\n### 金融企业\n\n- 花旗银行（上海、大连）\n\n- 招商银行（上海）\n- 宁波银行（总行）\n- 农商行（广州）\n- 平安金融（深圳）\n\n- 小雨点金融（重庆）\n\n\n\n### 国有企业\n\n- 中国电信（上海、安徽、广东）\n- 数字认证（北京）\n- 杭州市民卡\n- 杭州易建\n\n\n\n### 互联网企业\n\n- 华为（南京）\n- 新橙科技（北京）\n- 匠人网络（杭州）\n- 上海郎绿（上海）\n\n\n\n### 合作机构\n\n- 腾讯课堂\n- 咕泡学院\n- 叩丁狼教育"
  },
  {
    "path": "offline.html",
    "content": "---\nlayout: default\ntitle: Offline\nhide-in-nav: true\ndescription: \"阅读过的页面可以在离线时访问哦 ;)\"\nheader-img: \"img/404-bg.jpg\"\npermalink: /offline.html\n---\n\n\n<!-- Page Header -->\n{% include intro-header.html type=\"page\" %}\n\n<script>\n\tdocument.body.classList.add('page-fullscreen');\n</script>\n"
  },
  {
    "path": "package.json",
    "content": "{\n    \"name\": \"hux-blog\",\n    \"title\": \"Hux Blog\",\n    \"author\": \"Hux <huxpro@gmail.com>\",\n    \"version\": \"1.7.0\",\n    \"homepage\": \"http://huxpro.github.io\",\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"https://github.com/Huxpro/huxpro.github.io\"\n    },\n    \"bugs\": \"https://github.com/Huxpro/huxpro.github.io/issues\",\n    \"devDependencies\": {\n        \"grunt\": \"~0.4.5\",\n        \"grunt-contrib-less\": \"~0.11.4\",\n        \"grunt-contrib-watch\": \"~0.6.1\",\n        \"grunt-banner\": \"~0.2.3\",\n        \"grunt-contrib-uglify\": \"~0.5.1\"\n    },\n    \"scripts\": {\n        \"preview\": \"cd _site; python -m SimpleHTTPServer 8020\",\n        \"py3view\": \"cd _site; python3 -m http.server 8020\",\n        \"watch\"  : \"grunt watch & npm run preview & jekyll serve -w\",\n        \"py3wa\"  : \"grunt watch & npm run py3view & jekyll serve -w\",\n        \"boil\"   : \"git push boilerplate boilerplate:master\",\n        \"push\"   : \"git push origin master --tag\",\n        \"cafe\"   : \"git co gitcafe-pages; git merge master; git push gitcafe gitcafe-pages:gitcafe-pages --tag; git co master;\"\n    }\n}\n"
  },
  {
    "path": "sw.js",
    "content": "/* ===========================================================\n * sw.js\n * ===========================================================\n * Copyright 2016 @huxpro\n * Licensed under Apache 2.0\n * service worker scripting\n * ========================================================== */\n\n// CACHE_NAMESPACE\n// CacheStorage is shared between all sites under same domain.\n// A namespace can prevent potential name conflicts and mis-deletion.\nconst CACHE_NAMESPACE = 'main-'\n\nconst CACHE = CACHE_NAMESPACE + 'precache-then-runtime';\nconst PRECACHE_LIST = [\n  \"./\",\n  \"./offline.html\",\n  \"./js/jquery.min.js\",\n  \"./js/bootstrap.min.js\",\n  \"./js/hux-blog.min.js\",\n  \"./js/snackbar.js\",\n  \"./img/icon_wechat.png\",\n  \"./img/avatar-hux.jpg\",\n  \"./img/home-bg.jpg\",\n  \"./img/404-bg.jpg\",\n  \"./css/hux-blog.min.css\",\n  \"./css/bootstrap.min.css\"\n  // \"//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/css/font-awesome.min.css\",\n  // \"//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/fonts/fontawesome-webfont.woff2?v=4.6.3\",\n  // \"//cdnjs.cloudflare.com/ajax/libs/fastclick/1.0.6/fastclick.min.js\"\n]\nconst HOSTNAME_WHITELIST = [\n  self.location.hostname,\n  \"huangxuan.me\",\n  \"yanshuo.io\",\n  \"cdnjs.cloudflare.com\"\n]\nconst DEPRECATED_CACHES = ['precache-v1', 'runtime', 'main-precache-v1', 'main-runtime']\n\n\n// The Util Function to hack URLs of intercepted requests\nconst getCacheBustingUrl = (req) => {\n  var now = Date.now();\n  url = new URL(req.url)\n\n  // 1. fixed http URL\n  // Just keep syncing with location.protocol\n  // fetch(httpURL) belongs to active mixed content.\n  // And fetch(httpRequest) is not supported yet.\n  url.protocol = self.location.protocol\n\n  // 2. add query for caching-busting.\n  // Github Pages served with Cache-Control: max-age=600\n  // max-age on mutable content is error-prone, with SW life of bugs can even extend.\n  // Until cache mode of Fetch API landed, we have to workaround cache-busting with query string.\n  // Cache-Control-Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=453190\n  url.search += (url.search ? '&' : '?') + 'cache-bust=' + now;\n  return url.href\n}\n\n// The Util Function to detect and polyfill req.mode=\"navigate\"\n// request.mode of 'navigate' is unfortunately not supported in Chrome\n// versions older than 49, so we need to include a less precise fallback,\n// which checks for a GET request with an Accept: text/html header.\nconst isNavigationReq = (req) => (req.mode === 'navigate' || (req.method === 'GET' && req.headers.get('accept').includes('text/html')))\n\n// The Util Function to detect if a req is end with extension\n// Accordin to Fetch API spec <https://fetch.spec.whatwg.org/#concept-request-destination>\n// Any HTML's navigation has consistently mode=\"navigate\" type=\"\" and destination=\"document\"\n// including requesting an img (or any static resources) from URL Bar directly.\n// So It ends up with that regExp is still the king of URL routing ;)\n// P.S. An url.pathname has no '.' can not indicate it ends with extension (e.g. /api/version/1.2/)\nconst endWithExtension = (req) => Boolean(new URL(req.url).pathname.match(/\\.\\w+$/))\n\n// Redirect in SW manually fixed github pages arbitray 404s on things?blah\n// what we want:\n//    repo?blah -> !(gh 404) -> sw 302 -> repo/?blah\n//    .ext?blah -> !(sw 302 -> .ext/?blah -> gh 404) -> .ext?blah\n// If It's a navigation req and it's url.pathname isn't end with '/' or '.ext'\n// it should be a dir/repo request and need to be fixed (a.k.a be redirected)\n// Tracking https://twitter.com/Huxpro/status/798816417097224193\nconst shouldRedirect = (req) => (isNavigationReq(req) && new URL(req.url).pathname.substr(-1) !== \"/\" && !endWithExtension(req))\n\n// The Util Function to get redirect URL\n// `${url}/` would mis-add \"/\" in the end of query, so we use URL object.\n// P.P.S. Always trust url.pathname instead of the whole url string.\nconst getRedirectUrl = (req) => {\n  url = new URL(req.url)\n  url.pathname += \"/\"\n  return url.href\n}\n\n\n/**\n *  @Lifecycle Install\n *  Precache anything static to this version of your app.\n *  e.g. App Shell, 404, JS/CSS dependencies...\n *\n *  waitUntil() : installing ====> installed\n *  skipWaiting() : waiting(installed) ====> activating\n */\nself.addEventListener('install', e => {\n  e.waitUntil(\n    caches.open(CACHE).then(cache => {\n      return cache.addAll(PRECACHE_LIST)\n        .then(self.skipWaiting())\n        .catch(err => console.log(err))\n    })\n  )\n});\n\n\n/**\n *  @Lifecycle Activate\n *  New one activated when old isnt being used.\n *\n *  waitUntil(): activating ====> activated\n */\nself.addEventListener('activate', event => {\n  // delete old deprecated caches.\n  caches.keys().then(cacheNames => Promise.all(\n    cacheNames\n      .filter(cacheName => DEPRECATED_CACHES.includes(cacheName))\n      .map(cacheName => caches.delete(cacheName))\n  ))\n  console.log('service worker activated.')\n  event.waitUntil(self.clients.claim());\n});\n\n\nvar fetchHelper = {\n\n  fetchThenCache: function(request){\n    // Requests with mode \"no-cors\" can result in Opaque Response,\n    // Requests to Allow-Control-Cross-Origin: * can't include credentials.\n    const init = { mode: \"cors\", credentials: \"omit\" } \n\n    const fetched = fetch(request, init)\n    const fetchedCopy = fetched.then(resp => resp.clone());\n\n    // NOTE: Opaque Responses have no hedaders so [[ok]] make no sense to them\n    //       so Opaque Resp will not be cached in this case.\n    Promise.all([fetchedCopy, caches.open(CACHE)])\n      .then(([response, cache]) => response.ok && cache.put(request, response))\n      .catch(_ => {/* eat any errors */})\n    \n    return fetched;\n  },\n\n  cacheFirst: function(url){\n    return caches.match(url) \n      .then(resp => resp || this.fetchThenCache(url))\n      .catch(_ => {/* eat any errors */})\n  }\n}\n\n\n/**\n *  @Functional Fetch\n *  All network requests are being intercepted here.\n *\n *  void respondWith(Promise<Response> r);\n */\nself.addEventListener('fetch', event => {\n  // logs for debugging\n  //console.log(`fetch ${event.request.url}`)\n  //console.log(` - type: ${event.request.type}; destination: ${event.request.destination}`)\n  //console.log(` - mode: ${event.request.mode}, accept: ${event.request.headers.get('accept')}`)\n\n  // Skip some of cross-origin requests, like those for Google Analytics.\n  if (HOSTNAME_WHITELIST.indexOf(new URL(event.request.url).hostname) > -1) {\n\n    // Redirect in SW manually fixed github pages 404s on repo?blah\n    if (shouldRedirect(event.request)) {\n      event.respondWith(Response.redirect(getRedirectUrl(event.request)))\n      return;\n    }\n\n    // Cache-only Startgies for ys.static resources\n    if (event.request.url.indexOf('ys.static') > -1){\n      event.respondWith(fetchHelper.cacheFirst(event.request.url))\n      return;\n    }\n\n    // Stale-while-revalidate for possiblily dynamic content\n    // similar to HTTP's stale-while-revalidate: https://www.mnot.net/blog/2007/12/12/stale\n    // Upgrade from Jake's to Surma's: https://gist.github.com/surma/eb441223daaedf880801ad80006389f1\n    const cached = caches.match(event.request);\n    const fetched = fetch(getCacheBustingUrl(event.request), { cache: \"no-store\" });\n    const fetchedCopy = fetched.then(resp => resp.clone());\n    \n    // Call respondWith() with whatever we get first.\n    // Promise.race() resolves with first one settled (even rejected)\n    // If the fetch fails (e.g disconnected), wait for the cache.\n    // If there’s nothing in cache, wait for the fetch.\n    // If neither yields a response, return offline pages.\n    event.respondWith(\n      Promise.race([fetched.catch(_ => cached), cached])\n        .then(resp => resp || fetched)\n        .catch(_ => caches.match('offline.html'))\n    );\n\n    // Update the cache with the version we fetched (only for ok status)\n    event.waitUntil(\n      Promise.all([fetchedCopy, caches.open(CACHE)])\n        .then(([response, cache]) => response.ok && cache.put(event.request, response))\n        .catch(_ => {/* eat any errors */ })\n    );\n\n    // If one request is a HTML naviagtion, checking update!\n    if (isNavigationReq(event.request)) {\n      // you need \"preserve logs\" to see this log\n      // cuz it happened before navigating\n      console.log(`fetch ${event.request.url}`)\n      event.waitUntil(revalidateContent(cached, fetchedCopy))\n    }\n  }\n});\n\n\n/**\n * Broadcasting all clients with MessageChannel API\n */\nfunction sendMessageToAllClients(msg) {\n  self.clients.matchAll().then(clients => {\n    clients.forEach(client => {\n      console.log(client);\n      client.postMessage(msg)\n    })\n  })\n}\n\n/**\n * Broadcasting all clients async\n */\nfunction sendMessageToClientsAsync(msg) {\n  // waiting for new client alive with \"async\" setTimeout hacking\n  // https://twitter.com/Huxpro/status/799265578443751424\n  // https://jakearchibald.com/2016/service-worker-meeting-notes/#fetch-event-clients\n  setTimeout(() => {\n    sendMessageToAllClients(msg)\n  }, 1000)\n}\n\n/**\n * if content modified, we can notify clients to refresh\n * TODO: Gh-pages rebuild everything in each release. should find a workaround (e.g. ETag with cloudflare)\n * \n * @param  {Promise<response>} cachedResp  [description]\n * @param  {Promise<response>} fetchedResp [description]\n * @return {Promise}\n */\nfunction revalidateContent(cachedResp, fetchedResp) {\n  // revalidate when both promise resolved\n  return Promise.all([cachedResp, fetchedResp])\n    .then(([cached, fetched]) => {\n      const cachedVer = cached.headers.get('last-modified')\n      const fetchedVer = fetched.headers.get('last-modified')\n      console.log(`\"${cachedVer}\" vs. \"${fetchedVer}\"`);\n      if (cachedVer !== fetchedVer) {\n        sendMessageToClientsAsync({\n          'command': 'UPDATE_FOUND',\n          'url': fetched.url\n        })\n      }\n    })\n    .catch(err => console.log(err))\n}"
  }
]