[
  {
    "path": ".gitignore",
    "content": "_site\n_posts/_draft\n_posts/_archive\n_archive\nnode_modules\n.vscode\n*.log\n*.lock\n*.sh\n.DS_Store\n.jekyll-cache\n*/.DS_Store\n*/*/.DS_Store\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": "CNAME",
    "content": "huangxuan.me\n"
  },
  {
    "path": "Gemfile",
    "content": "source 'https://rubygems.org'\ngem 'jekyll-paginate'\n\ngem \"jekyll\", \"~> 4.0\"\ngem \"rake\"\n\ngem \"webrick\", \"~> 1.7\"\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": "[Hux Blog](https://huangxuan.me)\n================================\n\n> I never expected this to become popular.\n\n![](http://huangxuan.me/img/blog-desktop.jpg)\n\n\n[User Manual 👉](_doc/Manual.md)\n--------------------------------------------------\n\n### Getting Started\n\n1. You will need [Ruby](https://www.ruby-lang.org/en/) and [Bundler](https://bundler.io/) to use [Jekyll](https://jekyllrb.com/). Following [Using Jekyll with Bundler](https://jekyllrb.com/tutorials/using-jekyll-with-bundler/) to fullfill the enviromental requirement.\n\n2. Installed dependencies in the `Gemfile`:\n\n```sh\n$ bundle install \n```\n\n3. Serve the website (`localhost:4000` by default):\n\n```sh\n$ bundle exec jekyll serve  # alternatively, npm start\n```\n\n### Development (Build From Source)\n\nTo modify the theme, you will need [Grunt](https://gruntjs.com/). There are numbers of tasks you can find in the `Gruntfile.js`, includes minifing JavaScript, compiling `.less` to `.css`, adding banners to keep the Apache 2.0 license intact, watching for changes, etc. \n\nYes, they were inherited and are extremely old-fashioned. There is no modularization and transpilation, etc.\n\nCritical Jekyll-related code are located in `_include/` and `_layouts/`. Most of them are [Liquid](https://github.com/Shopify/liquid/wiki) templates.\n\nThis theme uses the default code syntax highlighter of jekyll, [Rouge](http://rouge.jneen.net/), which is compatible with Pygments theme so just pick any pygments theme css (e.g. from [here](http://jwarby.github.io/jekyll-pygments-themes/languages/javascript.html) and replace the content of `highlight.less`.\n\n\n### Interesting to know more? Checkout the [full user manual](_doc/Manual.md)!\n\n\nOther Resources\n---------------\n\nPorts\n- [**Hexo**](https://github.com/Kaijun/hexo-theme-huxblog) by @kaijun\n- [**React-SSR**](https://github.com/LucasIcarus/huxpro.github.io/tree/ssr) by @LucasIcarus\n\n[Starter/Boilerplate](https://github.com/huxpro/huxblog-boilerplate)\n- Out of date. Helps wanted for updating it on par with the main repo\n\nTranslation\n- [🇨🇳  中文文档（有点过时）](https://github.com/Huxpro/huxpro.github.io/blob/master/_doc/README.zh.md)\n\n\nLicense\n-------\n\nApache License 2.0.\nCopyright (c) 2015-present Huxpro\n\nHux Blog is derived from [Clean Blog Jekyll Theme (MIT License)](https://github.com/BlackrockDigital/startbootstrap-clean-blog-jekyll/)\nCopyright (c) 2013-2016 Blackrock Digital LLC.\n"
  },
  {
    "path": "Rakefile",
    "content": "require \"rubygems\"\nrequire 'rake'\nrequire 'yaml'\nrequire 'time'\n\nSOURCE = \".\"\nCONFIG = {\n  'version' => \"12.3.2\",\n  'themes' => File.join(SOURCE, \"_includes\", \"themes\"),\n  'layouts' => File.join(SOURCE, \"_layouts\"),\n  'posts' => File.join(SOURCE, \"_posts\"),\n  'post_ext' => \"md\",\n  'theme_package_version' => \"0.1.0\"\n}\n\n# Usage: rake post title=\"A Title\" subtitle=\"A sub title\"\ndesc \"Begin a new post in #{CONFIG['posts']}\"\ntask :post do\n  abort(\"rake aborted: '#{CONFIG['posts']}' directory not found.\") unless FileTest.directory?(CONFIG['posts'])\n  title = ENV[\"title\"] || \"new-post\"\n  subtitle = ENV[\"subtitle\"] || \"This is a subtitle\"\n  slug = title.downcase.strip.gsub(' ', '-').gsub(/[^\\w-]/, '')\n  begin\n    date = (ENV['date'] ? Time.parse(ENV['date']) : Time.now).strftime('%Y-%m-%d')\n  rescue Exception => e\n    puts \"Error - date format must be YYYY-MM-DD, please check you typed it correctly!\"\n    exit -1\n  end\n  filename = File.join(CONFIG['posts'], \"#{date}-#{slug}.#{CONFIG['post_ext']}\")\n  if File.exist?(filename)\n    abort(\"rake aborted!\") if ask(\"#{filename} already exists. Do you want to overwrite?\", ['y', 'n']) == 'n'\n  end\n\n  puts \"Creating new post: #{filename}\"\n  open(filename, 'w') do |post|\n    post.puts \"---\"\n    post.puts \"layout: post\"\n    post.puts \"title: \\\"#{title.gsub(/-/,' ')}\\\"\"\n    post.puts \"subtitle: \\\"#{subtitle.gsub(/-/,' ')}\\\"\"\n    post.puts \"date: #{date}\"\n    post.puts \"author: \\\"Hux\\\"\"\n    post.puts \"header-img: \\\"img/post-bg-2015.jpg\\\"\"\n    post.puts \"tags: []\"\n    post.puts \"---\"\n  end\nend # task :post\n\ndesc \"Launch preview environment\"\ntask :preview do\n  system \"jekyll --auto --server\"\nend # task :preview\n\n#Load custom rake scripts\nDir['_rake/*.rake'].each { |r| load r }\n"
  },
  {
    "path": "_config.yml",
    "content": "# Site settings\ntitle: Hux Blog\nSEOTitle: 黄玄的博客 | Hux Blog\nheader-img: img/home-bg.jpg\nemail: huxpro@gmail.com\ndescription: \"这里是 @Hux黄玄 的个人博客，与你一起发现更大的世界 | 要做一个有 swag 的程序员\"\nkeyword: \"黄玄, Hux黄玄, Hux, 鬼栈, huxpro, @huxpro, 黄玄的博客, Hux Blog, 博客, 个人网站, 互联网, Web, JavaScript, React, React Native, 前端, 设计\"\nurl: \"https://huangxuan.me\" # your host, for absolute URL\nbaseurl: \"\" # for example, '/blog' if your blog hosted on 'host/blog'\n\n# Publish posts or collection documents with a future date.\nfuture: true\n\n# SNS settings\nRSS: false\nweibo_username: huxpro\nzhihu_username: huxpro\ngithub_username: huxpro\ntwitter_username: huxpro\n#facebook_username:  huxpro\n#linkedin_username:  firstname-lastname-idxxxx\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:\n  [\n    \"less\",\n    \"node_modules\",\n    \"Gruntfile.js\",\n    \"package.json\",\n    \"README.md\",\n    \"README.zh.md\",\n  ]\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# 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# 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# Disqus settings\ndisqus_username: hux\n\n# Netease settings\nnetease_comment: false\n\n# Analytics settings\n# Baidu Analytics\n# ba_track_id: [your track id]\n\n# Google Analytics\nga_track_id: \"UA-49627206-1\" # Format: UA-xxxxxx-xx\nga_domain: huangxuan.me\n\n# Sidebar settings\nsidebar: true # whether or not using Sidebar.\nsidebar-about-description: \"要做一个有 swag 的程序员 <br> React Team @ Meta\"\nsidebar-avatar: https://github.com/Huxpro.png # use absolute URL, seeing it's used in both `/` and `/about/`\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# Progressive Web Apps\nchrome-tab-theme-color: \"#000000\"\nservice-worker: true\n\n# MathJax rendering for layout:page (e.g. post preview)\npage-mathjax: false\n\n# Friends\nfriends:\n  [\n    { title: \"乱序（Midare）\", href: \"http://mida.re/\" },\n    { title: \"Ebn Zhang\", href: \"https://ebnbin.dev/\" },\n    { title: \"Kun Qian\", href: \"http://kunq.me\" },\n    { title: \"Sherry Woo\", href: \"https://sherrywoo.me/\" },\n    { title: \"SmdCn\", href: \"http://blog.smdcn.net\" },\n    { title: \"JiyinYiyong\", href: \"http://tiye.me/\" },\n    { title: \"DHong Say\", href: \"http://dhong.co\" },\n    { title: \"尹峰以为\", href: \"http://ingf.github.io/\" },\n  ]\n"
  },
  {
    "path": "_doc/Manual.md",
    "content": "Hux Blog User Manual\n====================\n\n* Basics\n\t* [Getting Started](#getting-started)\n\t* [Development](#development)\n\t* [Config](#configs)\n\t* [Posts](#posts)\n* Components\n\t* [SideBar](#sidebar)\n\t* [Mini About Me](#mini-about-me)\n\t* [Featured Tags](#featured-tags)\n\t* [Friends](#friends)\n\t* [Keynote Layout](#keynote-layout)\n* Misc\n\t* [Comment](#comment)\n\t* [Analytics](#analytics)\n\t* [SEO Title](#seo-title)\n* [FAQ](#faq)\n* [Releases](#releases) \n\n\n### Getting Started\n\n1. You will need [Ruby](https://www.ruby-lang.org/en/) and [Bundler](https://bundler.io/) to use [Jekyll](https://jekyllrb.com/). Following [Using Jekyll with Bundler](https://jekyllrb.com/tutorials/using-jekyll-with-bundler/) to fullfill the enviromental requirement.\n\n2. Installed dependencies in the `Gemfile`:\n\n```sh\n$ bundle install \n```\n\n3. Serve the website (`localhost:4000` by default):\n\n```sh\n$ bundle exec jekyll serve  # alternatively, npm start\n```\n\n### Development\n\nTo modify the theme, you will need [Grunt](https://gruntjs.com/). There are numbers of tasks you can find in the `Gruntfile.js`, includes minifing JavaScript, compiling `.less` to `.css`, adding banners to keep the Apache 2.0 license intact, watching for changes, etc. \n\nYes, they were inherited and are extremely old-fashioned. There is no modularization and transpilation, etc.\n\nCritical Jekyll-related code are located in `_include/` and `_layouts/`. Most of them are [Liquid](https://github.com/Shopify/liquid/wiki) templates.\n\nThis theme uses the default code syntax highlighter of jekyll, [Rouge](http://rouge.jneen.net/), which is compatible with Pygments theme so just pick any pygments theme css (e.g. from [here](http://jwarby.github.io/jekyll-pygments-themes/languages/javascript.html) and replace the content of `highlight.less`.\n\n\n### Configs\n\nYou can easily customize the blog by modifying `_config.yml`:\n\n```yml\n# Site settings\ntitle: Hux Blog             # title of your website\nSEOTitle: Hux Blog          # check out docs for more detail\ndescription: \"Cool Blog\"    # ...\n\n# SNS settings      \ngithub_username: huxpro     # modify this account to yours\nweibo_username: huxpro      # the footer woule be auto-updated.\n\n# Build settings\npaginate: 10                # nums of posts in one page\n```\n\nFor more options, please check out [Jekyll - Official Site](http://jekyllrb.com/). \nMost of them are very descriptive so feel brave to dive into code directly as well. \n\n\n### Posts\n\nPosts are simply just Markdown files in the `_posts/`. \nMetadata of posts are listed in a YAML style _front-matter_.\n\nFor instance, [Hello 2015])(https://huangxuan.me/2015/01/29/hello-2015/) has the front-matter of this:\n\n```yml\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\"\ncatalog: true\ntags:\n    - Life\n    - Meta\n---\n```\n\n> Note: `tags` section can also be written as `tags: [Life, Meta]`.\n\nAfter [Rake](https://github.com/ruby/rake) is introduced, we can use the command below to simplify the post creation:\n\n```\nrake post title=\"Hello 2015\" subtitle=\"Hello World, Hello Blog\"\n```\n\nThis command will automatially generate a sample post similar as above under the `_posts/` folder.\n\nThere are a bunch of _advanced_ configs:\n\n1. a _text style_ header like [this](https://huangxuan.me/2019/09/08/spacemacs-workflow/) with\n\n```yml\nheader-style: text \n```\n\n2. Turning on Latex support:\n\n```yml\nmathjax: true\n```\n\n3. Adding a mask to the header picture:\n\n```yml\nheader-mask: 0.3\n```\n\nEtc.\n\n\n### SideBar\n\n![](http://huangxuan.me/img/blog-sidebar.jpg)\n\n**SideBar** provides possible modules to show off more personal information.\n\n```yml\n# Sidebar settings\nsidebar: true   # default true\nsidebar-about-description: \"your description here\"\nsidebar-avatar: /img/avatar-hux.jpg     # use absolute URL.\n```\n\nModules *[Featured Tags](#featured-tags)*, *[Mini About Me](#mini-about-me)* and *[Friends](#friends)* are turned on by default and you can add your own. The sidebar is naturally responsive, i.e. be pushed to bottom in a smaller screen (`<= 992px`, according to [Bootstarp Grid System](http://getbootstrap.com/css/#grid))  \n\n\n### Mini About Me\n\n**Mini-About-Me** displays your avatar, description and all SNS buttons if  `sidebar-avatar` and `sidebar-about-description` variables are set. \n\nIt would be hidden in a smaller screen when the entire sidebar are pushed to bottom. Since there is already SNS portion there in the footer.\n\n### Featured Tags\n\n**Featured-Tags** is similar to any cool tag features in website like [Medium](http://medium.com).\nStarted from V1.4, this module can be used even when sidebar is off and displayed always in the bottom. \n\n```yml\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\nThe only thing need to be paid attention to is `featured-condition-size`, which indicate a criteria that tags need to have to be able to \"featured\". Internally, a condition `{% if tag[1].size > {{site.featured-condition-size}} %}` are made.\n\n### Friends\n\nFriends is a common feature of any blog. It helps with SEO if you have a bi-directional hyperlinks with your friends sites.\nThis module can live when sidebar is off as well.\n\nFriends information is configured as a JSON string in `_config.yml`\n\n```yml\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\n![](http://huangxuan.me/img/blog-keynote.jpg)\n\nThere is a increased trend to use Open Web technology for keynotes and presentations via Reveal.js, Impress.js, Slides, Prezi etc. I consider a modern blog should have first-class support to embed these HTML based presentation so **Keynote layout** are made.\n\nTo use, in the **front-matter**:\n\n```yml\n---\nlayout:     keynote\niframe:     \"http://huangxuan.me/js-module-7day/\"\n---\n```\n\nThe `iframe` element will be automatically resized to adapt different form factors and device orientation. \nBecause most of the keynote framework prevent the browser default scroll behavior. A bottom-padding is set to help user and imply user that more content could be presented below.\n\n\n### Comment\n\n> Help Wanted: Moving to a Github-based solution.\n\nCurrently, [Disqus](http://disqus.com) <del> and [Duoshuo](http://duoshuo.com)</del> are supported as third party discussion system.\n\nFirst of all, you need to sign up and get your own account. **Repeat, DO NOT use mine!** (I have set Trusted Domains) It is deathly simple to sign up and you will get the full power of management system. Please give it a try!\n\nSecond, from V1.5, you can easily complete your comment configuration by just adding your **short name** into `_config.yml`:\n\n```yml\nduoshuo_username: _your_duoshuo_short_name_\n# OR\ndisqus_username: _your_disqus_short_name_\n```\n\n**To the old version user**, it's better that you pull the new version, otherwise you have to replace code in `post.html`, `keynote.html` and `about.html` on your own.\n\n<del>Furthermore, Duoshuo support Sharing. if you only wanna use Duoshuo comment without sharing, you can set `duoshuo_share: false`. </del>\n\n\n### Analytics\n\nFrom V1.5, Google Analytics and Baidu Tongji are supported with a simple config away:\n\n```yml\n# Baidu Analytics\nba_track_id: 4cc1f2d8f3067386cc5cdb626a202900\n\n# Google Analytics\nga_track_id: 'UA-49627206-1'            # Format: UA-xxxxxx-xx\nga_domain: huangxuan.me\n```\n\nJust checkout the code offered by Google/Baidu, and copy paste here, all the rest is already done for you.\n\n(Google might ask for meta tag `google-site-verification`)\n\n\n### SEO Title\n\nBefore V1.4, site setting `title` is not only used for displayed in Home Page and Navbar, but also used to generate the `<title>` in HTML.\nIt's possible that you want the two things different. For me, my site-title is **“Hux Blog”** but I want the title shows in search engine is **“黄玄的博客 | Hux Blog”** which is multi-language.\n\nSo, the SEO Title is introduced to solve this problem, you can set `SEOTitle` different from `title`, and it would be only used to generate HTML `<title>` and setting DuoShuo Sharing.\n\n\nFAQ\n---\n\n#### cannot load such file -- jekyll-paginate\n\nThis blog started in Jekyll 2 time when `jekyll-paginate` is standard. With Jekyll 3, it's a plugin we included in `_config.yml`.\n\nMake sure you installed it via plain `gem` CLI or Bundler.\n\n\nReleases\n--------\n\n#### V1.8.2\n\n- Merged #333, #336 from @JinsYin.\n- Add `Gemfile` due to increasing Bundler using.\n- TODO: `multilingual` could be more automative via configurations and convention.\n- Drop the entire `portfolio` page until a big rewrite of a better `project` page.\n\n#### V1.8.1\n\n- Improve multi-lingual implementation, see `about.html` or `_posts/2017-07-12-upgrading-eleme-to-pwa.markdown` for a example of uses.\n\n#### V1.8\n\n- Brand new [Archive](https://huangxuan.me/archive/) page! It combines previous Archive and Tag page and it's backward-cmpatible.\nShout out to [@kitian616/jekyll-TeXt-theme](https://github.com/kitian616/jekyll-TeXt-theme) of bringing this idea. \n- Improve engineering by extracting duplicated liquid templates into reuseable includes. This was proposed in #74 by @Kaijun but postponed for entirely 2.5 years! I wasn't able to merge his PR directly because of long-time divegence but the credit is belonging to @Kaijun.\n- Improved code block. Long-wanted line number are supported out of the box (thanks for @SmilingParadise's help from Sina Weibo), the default theme is updated to Atom One Dark as well (checkout FQA for how to change to your farovite theme)\n- MathJax support by @Voleking in #80. I choose to use the SVG renderer though. See [Mathjax, kramdown and Octopress](https://www.lucypark.kr/blog/2013/02/25/mathjax-kramdown-and-octopress/) for writing and escaping details.\n- Open Graph Protocol support by @Android-KitKat in #253\n- `header-img-credit` and `header-img-credit-href`\n- `nav-style: invert` and `header-style: text`\n\n#### V1.7\n\n- PWA / Service Worker support.\n\n#### v1.6\n\n- Change cdn to cdnjs for better HTTPS support\n\n#### V1.5.2\n\n- Feeling annoyed to delete my blog post after clone or pull? Try **Boilerplate (Beta)** to help you get started quickly and easily merge update.\n- `-apple-system` is added in font rule, which display beautiful new font **San Francisco** in iOS 9 by default.\n- Fixed [issue#15](https://github.com/Huxpro/huxpro.github.io/issues/15) about code wrap.\n\n#### V1.5.1\n\n- **[Comment](#comment)** support [**Disqus**](http://disqus.com) officially, thanks to @rpsh.\n\n#### V1.5\n\n- **[Comment](#comment)** and **[Analytics](#analytics)** is configurable now! We also add **Google Analytics support** and drop tencents. Both documents is updated.\n\n#### V1.4\n\n- **[Featured Tags](#featured-tags)** is now independent of [SideBar](#sidebar). Both documents is updated.\n- New **[SEO Title](#seo-title)** for SEO usage which is differ from the site title\n\n#### V1.3.1\n\n- Support **PingFang (苹方)**, the new Chinese font presented by [OS X El Capitan](http://www.apple.com/cn/osx/whats-new/)\n\n\n#### V1.3\n\n- Big Improvement to the **Navigation Menu** *(especially in Android)*:  Dropping the old, stuttering, low-performance [Bootstrap collapse.js](http://getbootstrap.com/javascript/#collapse),  replaced with an own wrote, [jank free](http://jankfree.org/) navbar menu in a pretty high-performance implementation of [Google Material Design](https://www.google.com/design/spec/material-design/introduction.html).\n\n<img src=\"http://huangxuan.me/img/blog-md-navbar.gif\" width=\"320\" />\n\n\n#### V1.2\n\n- Brand new **[Keynote Layout](#keynote-layout)** is provided for easily posting beautiful HTML presentations you have created with this blog\n\n\n#### V1.1\n\n- We now support a clean and gorgeous **[SideBar](#sidebar)** for displaying more info\n- **[Friends](#friends)** is also added as a common feature of blog help you do SEO\n\n#### V1.0\n\n- Full-feature **Tag** support\n- **Mobile first** user experience optimization\n- **Typographic optimization** for Chinese Fonts\n- **Network optimizaition** for China, dropping Google webfont, using local CDN\n- Using [Github Flavored Markdown](https://help.github.com/articles/github-flavored-markdown/)\n- Using Baidu, Tencent/QQ analytics\n- Using [DuoShuo](http://duoshuo.com/) as the Disqus-like third party discussion system"
  },
  {
    "path": "_doc/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在引入[Rake](https://github.com/ruby/rake)工具之后，我们可以使用命令：\n\n```\nrake post title=\"Hello 2015\" subtitle=\"Hello World, Hello Blog\"\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": "_includes/about/en.md",
    "content": "Hey, I am Huang, Xuan (a.k.a. _@huxpro_). I worked on the [React Team](https://beta.reactjs.org/community/meet-the-team#react-core) at <del>Facebook</del>Meta.\n\nI considered myself as a hybrid between a software engineer specifically into the programming languages theories and implementations domain (i.e. compiler, type system, type-based formal verification, virtual machine, runtime systems, garbage collection), and a creative technologiest deeply caring about many humanistic aspects (e.g. visual, sound, interaction) in UI and HCI in general.\n\nI also worked on the [Hermes JavaScript Engine](https://hermesengine.dev/), some other projects under the [Reality Labs (Research)](https://tech.fb.com/ar-vr/), and [ReasonML](https://reasonml.github.io/) (now [ReScript](https://rescript-lang.org/)) efforts at Meta (Facebook).\n\nIn the past, I worked on [Alitrip (Fliggy)](https://www.alitrip.com/) mobile and web apps under the [Alibaba Group](https://en.wikipedia.org/wiki/Alibaba_Group), found and lead front-end infrastructure team at an unicorn startup company [Beijing Weiying (a.k.a. WePiao, now acquired by Maoyan)](https://www.crunchbase.com/organization/beijing-weiying-technology), and helped [Ele.me (now acquired by Alibaba)](https://en.wikipedia.org/wiki/Ele.me) to upgrade their mobile web site into [the first influential PWA (progressive web app) in China](https://medium.com/elemefe/upgrading-ele-me-to-progressive-web-app-2a446832e509).\n\nI studied BA, Digital Media Art at [Communication University of China](https://en.wikipedia.org/wiki/Communication_University_of_China) and MS, Computer Science (with a focus on programming languages, mainly supervised by [Prof. Matthew Fluet](https://www.cs.rit.edu/~mtf/)) at [Rochester Institute of Technology](https://en.wikipedia.org/wiki/Rochester_Institute_of_Technology).\n\n##### Appearence\n\n- [React Labs: What We've Been Working On – June 2022][12] · React Blog · 2022\n- [React Without Memo][11] · [React Conf 2021](https://conf.reactjs.org/)\n- [Toward Hermes being the Default][11] · React Native Blog · 2021\n- React Native 0.64 with Hermes for iOS · [The RN Show Podcast Ep #5](https://www.callstack.com/podcast-react-native-show) · 2021\n- [Upgrading to Progressive Web Apps][9] · [JSConf China Shanghai 2017](http://2017.jsconf.cn/)\n- Building Progressive Web Apps · [CSDI Guangzhou 2017](http://www.csdisummit.com/)\n- The State of Progressive Web App · GDG IO Redux Beijing 2017\n- PWA Rehashing · Baidu HQ Beijing 2017\n- [Service Worker 101][5] · GDG DevFest Beijing 2016\n- [Progressive Web Apps][4] · QCon Shanghai 2016\n- Progressive Web App in my POV · GDG IO Redux Beijing 2016\n- [CSS Still Sucks 2015][2] · 2015\n- [JavaScript Modularization Journey][1] · 2015\n\n[1]: //huangxuan.me/2015/07/09/js-module-7day/\n[2]: //huangxuan.me/2015/12/28/css-sucks-2015/\n[3]: //huangxuan.me/2016/06/05/pwa-in-my-pov/\n[4]: //huangxuan.me/2016/10/20/pwa-qcon2016/\n[5]: //huangxuan.me/2016/11/20/sw-101-gdgdf/\n[6]: https://yanshuo.io/assets/player/?deck=58ac8598b123db0067292f92 \"PWA Rehashing\"\n[7]: https://yanshuo.io/assets/player/?deck=593ad6fbfe88c2006a0a0d6d \"The State of PWA\"\n[8]: https://yanshuo.io/assets/player/?deck=594d673d570c357d0698a950 \"Building PWA\"\n[9]: //huangxuan.me/jsconfcn2017/\n[10]: https://reactnative.dev/blog/2021/10/26/toward-hermes-being-the-default\n[11]: https://youtu.be/lGEMwh32soc\n[12]: https://reactjs.org/blog/2022/06/15/react-labs-what-we-have-been-working-on-june-2022.html\n"
  },
  {
    "path": "_includes/about/zh.md",
    "content": "Hey，我是黄玄（a.k.a. Hux, _@huxpro_），一个略懂计算机科学与艺术的斜杆不动青年，自诩是一个[广院](https://baike.baidu.com/item/%E4%B8%AD%E5%9B%BD%E4%BC%A0%E5%AA%92%E5%A4%A7%E5%AD%A6)数字媒体艺术系与 RIT 计算机科学系（师从 [Prof. Matthew Fluet](https://www.cs.rit.edu/~mtf/) 专攻编程语言）杂交出来的[黑客与画家](https://book.douban.com/subject/6021440/)。\n\n现为 <del>Facebook</del> Meta 签约软件工程师，就职于开源技术<del>网红</del>团队 [React](https://beta.reactjs.org/community/meet-the-team#react-core)，曾参与 [Hermes JavaScript 引擎](https://hermesengine.dev/)，[ReasonML](https://reasonml.github.io/) (现 [ReScript](https://rescript-lang.org/))，以及 [Reality Labs](https://tech.fb.com/ar-vr/) 某保密项目等。在国内期间，曾被招募为阿里巴巴 · [阿里旅行（飞猪）](http://alitrip.com)· 前端工程师、微影时代 · 微票儿 · 前端基础设施工程团队负责人、[饿了么](https://ele.me/) · 大前端团队 · [PWA 顾问](https://medium.com/elemefe/upgrading-ele-me-to-progressive-web-app-2a446832e509) 等。\n\n\n目前的物理活动范围主要在美帝纽约与硅谷，也想当个数字游<del>民</del>侠。虚拟分身日常出没于[微博](https://weibo.com/huxpro)、[知乎](https://www.zhihu.com/people/huxpro/pins/posts)、[B站](https://space.bilibili.com/43271611)、[Instagram](https://www.instagram.com/huxpro/)、[推特](https://twitter.com/Huxpro/)、[Github](https://github.com/huxpro) 等。\n\n\n##### 技术演讲\n\n- [我的大前端世界观][20] · [FEDAY](https://fequan.com/2023/) · 2023\n- [前端已死，前端永生][21] · [掘金年度技术演讲](https://juejin.cn/meetings/talk2023) · 2023\n- [React 国情咨文 2022][13] · 第七届中国开源年会 · 2023\n- [React Labs: What We've Been Working On – June 2022][12] · React Blog\n- [React Without Memo][11] · [React Conf 2021](https://conf.reactjs.org/)\n- [Toward Hermes being the Default][11] · React Native Blog · 2021\n- React Native 0.64 with Hermes for iOS · [The RN Show Podcast Ep #5](https://www.callstack.com/podcast-react-native-show) · 2021\n- [Upgrading to Progressive Web Apps][9] · [Youtube](https://www.youtube.com/watch?v=RWzMF-1fjJ8&t=1s) · [JSConf CN 上海 2017](http://2017.jsconf.cn/)\n- Building Progressive Web Apps · [CSDI 广州 2017](http://www.csdisummit.com/)\n- The State of Progressive Web App · GDG IO Redux 北京 2017\n- 炒冷饭 · PWA 到底是个什么玩意？· Baidu HQ 北京 2017\n- [Service Worker 101][5] · GDG DevFest 北京 2016\n- [Progressive Web App，复兴序章][4] · [QCon 上海 2016](http://2016.qconshanghai.com/presentation/3111)\n- Progressive Web App 之我见 · GDG IO Redux 北京 2016\n- [CSS Still Sucks 2015][2] · 2015\n- [JavaScript 模块化七日谈][1] · 2015\n\n\n##### 媒体关注\n\n- [Hux 黄玄：从全局视角看 React 生态][14] · 直播 · 图灵 8 点半 · 2023\n- [2022 中国开源先锋 33 人][18] · SegmentFault · 2023\n- [React 黄玄：不懂艺术的 B-Boy 不是 Swag 的程序员][16] · Gitee 封面人物 · 2022\n- [在硅谷当程序员是怎样的体验？][17] · 知乎[《我所向往的职业啊》](https://movie.douban.com/subject/36015036/) · 2022\n\n<!--\n- [掘金 AMA：我是前端娱乐圈的老人 & Facebook 实习生 -- 黄玄][19] · 2018\n-->\n\n\n[1]: //huangxuan.me/2015/07/09/js-module-7day/\n[2]: //huangxuan.me/2015/12/28/css-sucks-2015/\n[3]: //huangxuan.me/2016/06/05/pwa-in-my-pov/\n[4]: //huangxuan.me/2016/10/20/pwa-qcon2016/\n[5]: //huangxuan.me/2016/11/20/sw-101-gdgdf/\n[6]: https://yanshuo.io/assets/player/?deck=58ac8598b123db0067292f92 \"PWA Rehashing\"\n[7]: https://yanshuo.io/assets/player/?deck=593ad6fbfe88c2006a0a0d6d \"The State of PWA\"\n[8]: https://yanshuo.io/assets/player/?deck=594d673d570c357d0698a950 \"Building PWA\"\n[9]: //huangxuan.me/jsconfcn2017/\n[10]: https://reactnative.dev/blog/2021/10/26/toward-hermes-being-the-default\n[11]: https://youtu.be/lGEMwh32soc\n[12]: https://reactjs.org/blog/2022/06/15/react-labs-what-we-have-been-working-on-june-2022.html\n[13]: https://www.bilibili.com/video/BV1LY411Q7hC/?spm_id_from=333.999.0.0\n[14]: https://appycyfaqcq1951.pc.xiaoe-tech.com/p/t_pc/course_pc_detail/video/v_64477dbfe4b0cf39e6c11d2a\n[15]: https://segmentfault.com/a/1190000043208486\n[16]: https://gitee.com/gitee-stars/30\n[17]: https://www.zhihu.com/zvideo/1542577108190068737?page=ogv\n[18]: https://segmentfault.com/a/1190000043208486\n[19]: https://juejin.cn/post/6844903750155419655\n[20]: https://www.bilibili.com/video/BV1SC4y1c7ju/\n[21]: https://www.bilibili.com/video/BV1uz421d7Ch/\n"
  },
  {
    "path": "_includes/ads.html",
    "content": "<script async src=\"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\"></script>\n<!-- first shot -->\n<ins class=\"adsbygoogle\" style=\"display:block\" data-ad-client=\"ca-pub-6487568398225121\" data-ad-slot=\"4814308751\"\n     data-ad-format=\"auto\" data-full-width-responsive=\"true\"></ins>\n<script>\n     (adsbygoogle = window.adsbygoogle || []).push({});\n</script>\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>__SEPARATOR__\n        {% endif %}{% endfor %}\n        {% endcapture %}\n        {{ tags | split:'__SEPARATOR__' | sort }}\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 style=\"margin-left: 2px; margin-bottom:-5px;\" frameborder=\"0\" scrolling=\"0\" width=\"100px\"\n                        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<!-- Simple Jekyll Search -->\n<script src=\"{{ \"/js/simple-jekyll-search.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{% if page.plchart %}\n<!-- jquery.tagcloud.js -->\n<script>\n    // https://stackoverflow.com/questions/9975810/make-iframe-automatically-adjust-height-according-to-the-contents-without-using\n    function resizeIframe(obj) {\n        obj.style.height = obj.contentWindow.document.body.scrollHeight + 'px';\n    }\n\n    $(document).ready(function () {\n        var $chart = document.querySelector(\"#chart\");\n        $chart.onload = function () {\n            resizeIframe($chart)\n        }\n        window.addEventListener(\"resize\", () => {\n            resizeIframe($chart)\n        });\n    })\n</script>\n{% endif %}\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) {\n        i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {\n            (i[r].q = i[r].q || []).push(arguments)\n        }, 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{% unless page.no-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{% endunless %}\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    // Changes at v1.8.1: include lang flag as a url query. This interop well with catalog hash anchors.\n    function getLang() { return new URLSearchParams(document.location.search).get(\"lang\") }\n\n    function setLang(newLang) {\n        var params = new URLSearchParams(document.location.search)\n        params.set(\"lang\", newLang)\n        document.location.search = params.toString()  // refresh.\n    }\n\n    // handle render\n    function _render() {\n        var lang = getLang()\n        // en\n        if (lang == \"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            // default to zh-cn\n        } else {\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        generateCatalog(\".catalog-body\");\n    }\n\n    // handle select change\n    function onLanChange(index) {\n        if (index == 0) {\n            lang = \"zh\"\n        } else {\n            lang = \"en\"\n        }\n        setLang(lang)\n    }\n\n    // init\n    _render();\n</script>\n{% endif %}\n\n<!-- Simple Jekyll Search -->\n<script>\n    // https://stackoverflow.com/questions/1912501/unescape-html-entities-in-javascript\n    function htmlDecode(input) {\n        var e = document.createElement('textarea');\n        e.innerHTML = input;\n        // handle case of empty input\n        return e.childNodes.length === 0 ? \"\" : e.childNodes[0].nodeValue;\n    }\n\n    SimpleJekyllSearch({\n        searchInput: document.getElementById('search-input'),\n        resultsContainer: document.getElementById('search-results'),\n        json: '/search.json',\n        searchResultTemplate: '<div class=\"post-preview item\"><a href=\"{url}\"><h2 class=\"post-title\">{title}</h2><h3 class=\"post-subtitle\">{subtitle}</h3><hr></a></div>',\n        noResultsText: 'No results',\n        limit: 50,\n        fuzzy: false,\n        // a hack to get escaped subtitle unescaped. for some reason, \n        // post.subtitle w/o escape filter nuke entire search.\n        templateMiddleware: function (prop, value, template) {\n            if (prop === 'subtitle' || prop === 'title') {\n                if (value.indexOf(\"code\")) {\n                    return htmlDecode(value);\n                } else {\n                    return value;\n                }\n            }\n        }\n    });\n\n    $(document).ready(function () {\n        var $searchPage = $('.search-page');\n        var $searchOpen = $('.search-icon');\n        var $searchClose = $('.search-icon-close');\n        var $searchInput = $('#search-input');\n        var $body = $('body');\n\n        $searchOpen.on('click', function (e) {\n            e.preventDefault();\n            $searchPage.toggleClass('search-active');\n            var prevClasses = $body.attr('class') || '';\n            setTimeout(function () {\n                $body.addClass('no-scroll');\n            }, 400)\n\n            if ($searchPage.hasClass('search-active')) {\n                $searchClose.on('click', function (e) {\n                    e.preventDefault();\n                    $searchPage.removeClass('search-active');\n                    $body.attr('class', prevClasses);  // from closure \n                });\n                $searchInput.focus();\n            }\n        });\n    });\n</script>"
  },
  {
    "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\"\n        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\"\n        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\"\n        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\n    <!-- Google AdSense -->\n    <script data-ad-client=\"ca-pub-6487568398225121\" async\n        src=\"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\"></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' or include.type == 'page' %}\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{% endif %}\n\n{% if include.type == 'post' %}\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=\"header-mask\"></div>\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 %}\n"
  },
  {
    "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/multilingual-sel.html",
    "content": "                <!-- 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"
  },
  {
    "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 }}/\">Home</a>\n                        </li>\n                        {% for page in site.pages %}\n                        {% if page.title and page.hide-in-nav != true %}\n                        <li>\n                            <a href=\"{{ page.url | prepend: site.baseurl }}\">{{ page.title }}</a>\n                        </li>\n                        {% endif %}\n                        {% endfor %}\n                        <li class=\"search-icon\">\n                            <a href=\"javascript:void(0)\">\n                                <i class=\"fa fa-search\"></i>\n                            </a>\n                        </li>\n                    </ul>\n                </div>\n            </div>\n            <!-- /.navbar-collapse -->\n        </div>\n        <!-- /.container -->\n    </nav>\n\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\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\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        /**\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>"
  },
  {
    "path": "_includes/posts/2017-07-12-upgrading-eleme-to-pwa/en.md",
    "content": "> Read at medium.com: [Upgrading Ele.me to Progressive Web Apps](https://medium.com/elemefe/upgrading-ele-me-to-progressive-web-app-2a446832e509)\n\nSince the very first experiments that [@Vue.js tweeted][1], we at Ele.me (the biggest food ordering and delivering company in China) have been working on upgrading our mobile website to a [Progressive Web App][2]. We’re proud to ship the world-first PWA exclusively for the Chinese market, but even prouder to collaborate with Google, UC and Tencent to push the boundary of web experience and browser supports in China.\n\n\n## Multi-page, Vue, PWA?\n\nThere is a prevailing opinion that only structuring a web app as a Single Page App can we build PWAs that deliver app-like user experience. Popular reference examples including [Twitter Lite][3], [Flipkart Lite][4], [Housing Go][5] and [Polymer Shop][6] are all using the SPA model.\n\nHowever at Ele.me, we’ve come to appreciate many advantages of a Multi-Page App model, and decided to refactor the mobile site from an Angular 1 SPA to a Multi-Paged app more than a year ago. The most important advantage we see is the isolation and decoupling between pages, which allows us to built different parts of the mobile site as “micro-services”. These services can then be independently iterated, embedded into 3rd-party apps, and even maintained by different teams.\n\nMeanwhile, we still leverage [Vue.js](http://vuejs.org/) to boost our productivity. You may have heard of Vue as a rival of React or Angular, but Vue's lightweight and performance make it also a perfect replacement of traditional \"jQuery/Zepto + template engine\" stack when engineering a Multi-page app. We built every component as [Single File Components](http://vuejs.org/v2/guide/single-file-components.html) so they can be easily shareable between pages. The declarative-ness plus reactivity Vue offered help us manage both code and data flow. Oh, did I mention that [Vue is progressive](https://www.youtube.com/watch?v=pBBSp_iIiVM)? So things like Vuex or Vue-Router can be incrementally adopted if our site's complexity scales up, like...migrating to SPA again? (Who knows...)\n \nIn 2017, PWA seems to be all the rage, so we embark on exploring how far can our Vue-based Multi-page PWAs actually go.\n\n## Implementing \"PRPL\" with MPA\n\nI love [PRPL pattern][7] because it gives you a high-level abstraction of how to structure and design your own PWA systems. Since we are not rebuild everything from scratch, we decided taking implementing PRPL as our migration goal:\n\n### 1. PUSH critical resources for initial URL route. \n\nThe key of pushing/preloading is to prioritize resources hidden in deep dependency graph and make browser's network stack busy ASAP. Let's say you have a SPA with code splitting by route, you can push/preload chunks for the current route before the \"entry chunks\" (e.g. webpack manifest, router) finish downloading and evaluating. So when the actual fetches happen, they might already be in caches.\n\nRoutes in MPAs naturally fetch code for that route only, and tend to have a flattening dependency graph. Most scripts depended by Ele.me are just `<script>` elements, so they can be found and fetched by [good old browser preloader][8] in early parsing phase without explicit `<link rel=\"preload\">`. \n\n![](/img/in-post/post-eleme-pwa/PUSH-link-rel-preload.jpg)\n\nTo take benefits from HTTP2 Multiplexing, we currently serve all critical resources under a single domain (no more domain sharding), and we are also experimenting on Server Push.\n\n\n### 2. RENDER initial route & get it interactive ASAP\n\nThis one is essentially free (ridiculously obvious) in MPA since there's only one route at one time.\n\nA straightforward rendering is critical for metrics such as First-Meaningful-Paint and Time-To-Interactive. MPAs gain it for free due to the simplicity of traditional HTML navigation they used. \n\n### 3. **PRE-CACHE** remaining routes using Service Worker\n\nThis's the part [Service Worker][9] come to join the show. Service Worker is known as a client-side proxy enabling developers to intercept requests and serve responses from cache, but it can also perform initiative fetch to prefetch then precache future resources.\n\n![](/img/in-post/post-eleme-pwa/PRECACHE-future-routes.jpg)\n\nWe already used [Webpack][10] in the build process to do `.vue` compilation and asset versioning, so we create a webpack plugin to help us collecting dependencies into a \"precache manifest\" and generating a new Service Worker file after each build. This is pretty much like [how SW-Precache works][11]. \n\n**In fact, we only collect dependencies of routes we flagged as \"Critical Route\".** You can think of them as [\"App Shell\"][12] or the \"Installation Package\" of our app. Once they are cached/installed successfully, our web app can boot up directly from cache and available offline. Routes that \"not critical\" would be incrementally cached at runtime during the first visit. Thanks to the LRU cache policies and TTL invalidation mechanisms provided by [SW-Toolbox][13], we have no worries of hitting the quota in a long run.\n\n### 4. LAZY-load & instantiate remaining routes on demand \n\nLazy-loading and lazily instantiating remaining parts of the app is relatively challenging for SPA to achieve. It requires both code splitting and async importing. Fortunately, this is also a built-in feature of MPA model, in which routes are naturally separated.\n\nNoticed that the lazy-loading can be done instantly if the requested route is already pre-cached in Service Worker cache, no matter whether SPA or MPA is used. #ServiceWorkerAwesomeness \n\n\n---\n\nSurprisingly, we found Multi-page PWA is kinda naturally \"PRPL\"! MPA has already provided built-in support for \"PRL\", and the second \"P\" involving Service Worker can be easily fulfilled in any PWA. \n\nSo what about the end result?\n\n\n![](/img/in-post/post-eleme-pwa/Lighthouse-before.png)\n\n**In [Lighthouse](https://developers.google.com/web/tools/lighthouse/) simulation (3G & 5x Slower CPU), we made Time-To-Interactive around 2 seconds,** and this was benchmarked on our HTTP1 test server. \n\nThe first visit is fast. The repeat visit with Service Worker is even faster. You can check out this video to see the huge difference between with or without Service Worker:\n\n<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/mbi_WnunJa8\" frameborder=\"0\" allowfullscreen></iframe>\n\nDid you see that? No, I mean the annoying blank screen. Even in the Service Worker one, the blank screen is still conspicuous during navigating. How can that be?\n\n\n## Multi-page Pitfall: Redo Everything!\n\nUnlike SPA, changing routes in MPA means actual browser navigation happens: The previous page is discarded completely and the browser need to redo everything for next route: re-download resources, re-parse HTML, re-evaluate JavaScript, re-decode image data, re-layout the page and re-paint the screen, even many of them could be shared across routes. All of these works combined requires significant computing and time.\n\nSo here is the profile (2x slower CPU simulated) of our entry page (most heavy one). Even we can make Time-To-Interactive around 1s in repeat visit, our users can still feel too slow for just \"switching a tab\".\n\n![](/img/in-post/post-eleme-pwa/msite-Before-Optim.png)\n\n### Huge JavaScript Re-Startup Cost\n\nAccording to the profile, most of the time (900ms) before hitting the first paint is spent on evaluating JavaScript. Half is on dependencies including Vue Runtime, components, libraries etc., another half is on actual Vue starting-up and mounting. Because all UI rendering is depended on JavaScript/Vue, all of the critical scripts remain guiltily parser-blocking. I'm by no means blaming JavaScript or Vue's overheads here, It's just a tradeoff when we need this layer of abstraction in engineering.\n\n**In SPA, JavaScript Start-up Cost is amortized during the whole lifecycle.** Parsing/Compiling for each script is only once, many heavy executing can be only once. The big JavaScript objects like Vue's ViewModels and Virtual DOM can be kept in memory and reused as much as you want. **This is not the case in MPA however.**\n\n\n### Could Browser Caches Help?\n\nYes or no.\n\nV8 introduced [code caching](http://v8project.blogspot.com/2015/07/code-caching.html), a way to store a local copy of compiled code so fetching, parsing and compilation could all be skipped next time. As @addyosmani mentioned in [JavaScript Start-up Performance](https://medium.com/reloading/javascript-start-up-performance-69200f43b201), scripts stored in Cache Storage via Service Worker could trigger code caching in just the first execution.\n\nAnother browser cache you might hear of is \"Back-Forward Cache\", or bfcache. The name varies, like Opera's \"Fast History Navigation\" or [WebKit's \"Page Cache\"](https://webkit.org/blog/427/webkit-page-cache-i-the-basics/). **The idea is that browsers can keep the previous page live in memory, i.e. DOM/JS states, instead of destroying everything.** In fact, this idea works very well for MPA. You can try every traditional Multi-page websites in iOS Safari and observe an instantaneously loading when back/forward. (With browser UI/Gesture or with hyperlink can have a slight difference though.)\n\nUnfortunately, Chrome has no this kind of in-memory bfcache currently concerning to memory consumption and its multi-process architecture. It just leverages HTTP disk cache to simplify the loading pipeline, almost everything still needs to be redone. More details and discussions can be seen [here](https://docs.google.com/document/d/1o8KImLPrJQcMNqvd_a-1V8fEVgtVeEJww453ZQ1hGuo/edit#).\n\n\n\n\n## Striving for Perceived Performance\n\nAlthough the reality is dark, we don't want to give up so easily. One optimization we try to do is to render DOM nodes/create Virtual DOM nodes as less as possible to improve the Time-To-Interactive. While another opportunity we see is to play tricks on perceived performance.\n\n@owencm have written a great post [\"Reactive Web Design: The secret to building web apps that feel amazing\"](https://medium.com/@owencm/reactive-web-design-the-secret-to-building-web-apps-that-feel-amazing-b5cbfe9b7c50) covering both \"Instant loads with skeleton screens\" and \"Stable loads via predefined sizes on elements\" to improve perceived performance and user experience. Yes, we actually used both. \n\nWhat about we showing the end result after these optimizations first before entering technical nitty gritty? There you go!\n\n<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/K5JBGnMYO1s\" frameborder=\"0\" allowfullscreen></iframe>\n\nToo fast and can not see the pulsing Skeleton Screen clearly? Here is a version showing how it looks like under 10 times slower CPU.\n\n<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/w1ZbNsHmRjs\" frameborder=\"0\" allowfullscreen></iframe>\n\nThis is a much better UX, right? Even we have slow navigation in slow devices, at least the UI is stable, consistent and always responding. So how we get there?\n\n\n\n### Rendering Skeleton Screen with Vue at Build-Time \n\nAs you might have guessed, the Skeleton Screen that consists of markups, styles, and images is inlined into `*.html` of each route. So they can be cached by Service Worker, be loaded instantly, and be rendered independently with any JavaScript.\n\nWe don't want to manually craft each Skeleton Screen for each routes. It's a tedious job and we have to manually sync every change between Skeleton Screens and the actual UI components (Yes we treat every route as just a Vue component). But think about it, [Skeleton Screen is just a blank version of a page into which information is gradually loaded](https://www.lukew.com/ff/entry.asp?1797). What if we bake the Skeleton Screen into the actual UI component as just a loading state so we can render Skeleton Screen out directly from it without the issue of syncing?\n\nThanks to the versatility of Vue, we can actually realize it with [Vue.js Server-Side Rendering](https://ssr.vuejs.org/en/). Instead of using it on a real server, we use it at build time to render Vue components to strings and injected them into HTML templates.\n\n\n\n### Fast Skeleton Painting...\n\nHaving markups in `*.html` doesn't mean that they will be painted fast, you have to make sure the [Critical Rendering Path](https://developers.google.com/web/fundamentals/performance/critical-rendering-path/) is optimized for that. Many developers believed that putting script tags in the end of the body is sufficient for getting content painted before executing scripts. This might be true for browsers supporting rendering an incomplete DOM tree (e.g. streaming render), But browsers might not do that in mobile concerning slower hardwares, battery, and heats. **And even we are told that script tags with `async` or `defer` is not parser-blocking, it also doesn't mean we can get content painted before executing scripts in reality.**\n\n![](https://html.spec.whatwg.org/images/asyncdefer.svg)\n\nFirst I want to clarify it a little bit. According to the [Scripting section of HTML](https://www.w3.org/TR/html51/semantics-scripting.html#elementdef-script) (WHATWG living standard, the W3C's same here), `async` scripts would be evaluated as soon as it is available thus could potentially blocking parsing. Only `defer` (and not inlined) is specified to be never block parsing. That's why [Steve Souders](http://stevesouders.com/) ever posted [\"Prefer DEFER Over ASYNC\"](https://calendar.perfplanet.com/2016/prefer-defer-over-async/). (`defer` has its own issue and we will cover it later.)\n\n**Then I want to say: A script not blocking parser could still block painting nonetheless.** So here is a reduced test I wrote named **\"Minimal Multi-page PWA\"**, or MMPWA, which basically render 1000 list items within an `async` (and truly not parser-blocking) script to see if we can get Skeleton Screen painted before scripts get executed. The profile below (over USB debugging on my real Nexus 5) shows my ignorance:\n\n![](/img/in-post/post-eleme-pwa/thisTick-&-Load.png)\n\nYes, keep your mouth open. The first paint is blocked. I was also surprised here. The reason I guess is that **if we touch DOM so quickly that the browser has still NOT finished previous painting job, our dear browser has to abort every pixel it has drawn, and has to wait until current DOM manipulation task finished and redo the rendering pipeline again.** And this more often happens with a mobile device with a slower CPU/GPU.\n\n### Fast Skeleton Painting with setTimeout Hack\n\nWe indeed encountered this problem when testing our new beautiful Skeleton Screen. Perhaps Vue finishes its job and start to mount nodes too fast ;). But anyway we have to make it slower, or rather lazier. So we try to put DOM manipulation things inside `setTimeout(callback, 0)`, and it works like a charm!\n\n![](/img/in-post/post-eleme-pwa/nextTick-&-Load.png)\n\n\nI think you may curious about how this change performs in the wild, so I have refined MMPWA by rendering 5000 list items rather 1000 to make the differences more obvious, and by designing it in an A/B testing manner. The code is on [Github](https://github.com/Huxpro/mmpwa) and the demo is live on [huangxuan.me/mmpwa/](https://huangxuan.me/mmpwa). Here is also a video for loungers.\n\n\n<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/3Ws7XBHrPD8\" frameborder=\"0\" allowfullscreen></iframe>\n\n\nThis famous `setTimeout` hack (a.k.a. Zero Delays) looks quite magic,  but it is science™. If you are familiar with **event loop**, it just prevents these code from executing in the current loop by putting everything to the **task queues** with the Timer Callback, so the browser could breath (update the rendering) in the main thread. \n\n\nSo we applied what we learned from MMPWA by putting `new Vue()` inside `setTimeout` and BOOM! We have Skeleton Screen painted consistently after every navigating! Here is the profile after all these optimizations.\n\n![](/img/in-post/post-eleme-pwa/msite-After-Optim.png)\n\nHuge improvements right?  This time we hit First Paint (Skeleton Screen Paint) at 400ms and TTI at 600ms. You should really go back to have a before-after comparison in details.\n\n### One more thing that I deferred\n\nBut wait, why is there still a bunch of guiltily parser-blocking scripts? Are them all `async`? OK, ok. For historical reasons, we do keep some parser-blocking scripts, like [lib-flexible](https://github.com/amfe/lib-flexible), we couldn't get rid of it without a huge refactoring. But most of these blocking scripts are in fact `defer`. We expected that they can be executed after parsing and in order, however the profile kinda slap on my face. :(\n\nRemember I said I would talk about one issue of `defer` previously? Yes, that's it. I have had a [conversation](https://twitter.com/Huxpro/status/859842124849827841) with [Jake Archibald](https://twitter.com/jaffathecake) and it turns out it might be a bug of Chrome when the deferred scripts are fully cached. [Vote it at crbug](https://bugs.chromium.org/p/chromium/issues/detail?id=717979)!\n\n\nSimilar improvements can be seen from Lighthouse (Under same server and network environment). A Pro Tip is you should always use lighthouse in a variable controlling approach.\n\n![](/img/in-post/post-eleme-pwa/Lighthouse-after.png)\n\n\n### Performance In the Real World\n\n[Alex Russell](https://medium.com/@slightlylate) has given [a very insightful talk on mobile web performance](https://youtu.be/4bZvq3nodf4?list=PLNYkxOF6rcIBTs2KPy1E6tIYaWoFcG3uj) at Chrome Dev Summit 2016, talking about how hard can we build performant web applications on mobile devices. Highly recommended.\n\nChinese users tend to have a pretty powerful phone. MI4 is shipped with snapdragon 801 (slightly out-performs Nexus 5) but only costs 100$. It’s affordable by at least 80% of our users so we take it as a baseline.\n\nHere is a video screen-recorded on my Nexus 5 showing switching between 4 tabs. The performance varies between tabs due to their variant scale. The heaviest one, entry page, take around 1s to hit real Time-To-Interactive on my Nexus 5.\n\nFYI. This is surprisingly comparable to what I get from Chrome Simulation with 2x CPU throttling. With 5x throttling, this can spend 2–3s to get TTI, horribly. (To be honest, I found even under same throttling, the results can vary drastically depended on my Macbook’s “mood”.)\n\n<iframe width=\"700\" height=\"525\" src=\"https://www.youtube.com/embed/ZLc8jysMqaw?ecver=1\" frameborder=\"0\" allowfullscreen></iframe>\n\n\n## Final Thoughts\n\n\nThis article is much longer than I could imagine. I am really appreciated if you could get here. So what can we learn from it?\n\n### MPA still has some way to go\n\n[Jake Archibald](https://twitter.com/jaffathecake) ever said that \"PWA !== SPA\" at [Chrome Dev Summit 2016](https://youtu.be/J2dOTKBoTL4?list=PLNYkxOF6rcIBTs2KPy1E6tIYaWoFcG3uj). But the sad truth is that even we have taken advantages of bleeding edge technologies such as \"PRPL\" pattern, Service Worker, App-Shell, Skeleton Screen, there is still a distance between us and many Single Page PWA just because we are Multi-page structured.\n\nThe web is extremely versatile. Static blogs, e-business sites, desktop-level software, all of them should be the first-class citizens of the web family. MPA might have things like \"bfcache API\", navigation transitions to catch up the SPA in the future, but it is not today certainly. \n\n\n### PWA is Awesome No Matter What\n\nHey, I am not overblowing it. Even we as a Multi-page PWA couldn't be as stunning and app-like as many Single Page PWAs are. The idea and technologies behind PWA still help us deliver a much better experience to our users on the web that hasn’t been possible before.\n\nWhat PWA is trying to solve are some fundamental problems of current web application model such as its hard dependencies to network and browser UIs. That' why PWA can be always beneficial no matter what architecture or what framework you actually used. [Addy Osmani](https://medium.com/@addyosmani) would give a talk [Production Progressive Web Apps With JavaScript Frameworks](https://events.google.com/io/schedule/?section=may-19&sid=e8436b55-ea89-4243-a644-5ecb319d9ef0) at this year's I/O (and [I/O 16](https://youtu.be/srdKq0DckXQ?list=PLNYkxOF6rcIDz1TzmmMRBC-kd8zPRTQIP)). You won’t want to miss it!\n\n---\n\nFinally, I’d love to thank:\n\n- my colleagues [YiSi Wang](https://github.com/YiSiWang), [GuangHui Ren](https://github.com/rguanghui), [JiyinYiyong](https://medium.com/@jiyinyiyong) from Eleme\n- collaborator [Michael Yeung](https://medium.com/@micyeung), [Liam Spradlin](https://medium.com/@LiamSpradlin) and other collaborators from Google\n- collaborators from UC/Tencent\n\nAnd special thanks to \n\n- invited reviewer, [Evan You](https://medium.com/@youyuxi).\n- Chrome “StackOverflow”, [Jake Archibald](https://twitter.com/jaffathecake).\n\nThank you all!\n\n---\n\n## Appendix. Architecture Diagram\n\n![](/img/in-post/post-eleme-pwa/Architecture.png)\n\n\n\n[1]: https://twitter.com/vuejs/status/834087199008239619\n[2]: https://developers.google.com/web/progressive-web-apps/\n[3]: https://blog.twitter.com/2017/how-we-built-twitter-lite\n[4]: https://medium.com/progressive-web-apps/building-flipkart-lite-a-progressive-web-app-2c211e641883\n[5]: https://medium.com/engineering-housing/progressing-mobile-web-fac3efb8b454\n[6]: https://shop.polymer-project.org/\n[7]: https://developers.google.com/web/fundamentals/performance/prpl-pattern/\n[8]: https://calendar.perfplanet.com/2013/big-bad-preloader/\n[9]: https://w3c.github.io/ServiceWorker/v1/\n[10]: https://webpack.github.io/\n[11]: https://medium.com/@Huxpro/how-does-sw-precache-works-2d99c3d3c725\n[12]: https://developers.google.com/web/updates/2015/11/app-shell\n[13]: https://googlechrome.github.io/sw-toolbox/\n"
  },
  {
    "path": "_includes/posts/2017-07-12-upgrading-eleme-to-pwa/zh.md",
    "content": "\n\n> 很荣幸在今年 2 月到 5 月的时间里，以顾问的身份加入饿了么，参与 PWA 的相关工作。这篇文章其实最初是在以英文写作发表在 medium 上的：[Upgrading Ele.me to Progressive Web Apps](https://medium.com/elemefe/upgrading-ele-me-to-progressive-web-app-2a446832e509)，获得了一定的关注。所以也决定改写为中文版本再次分享出来，希望能对你有所帮助 ;) <br><br>\n> 本文首发于 [CSDN](http://geek.csdn.net/news/detail/210535) 与《程序员》2017 年 7 月刊，同步发布于 [饿了么前端 - 知乎专栏](https://zhuanlan.zhihu.com/ElemeFE)、[Hux Blog](https://huangxuan.me)，转载请保留链接。\n\n\n自 Vue.js 官方推特第一次[公开][1]到现在，我们就一直在进行着将[饿了么移动端网站](https://h5.ele.me/msite/#pwa=true)升级为 [Progressive Web App][2] 的工作。直到近日在 Google I/O 2017 上[登台亮相](https://m.weibo.cn/status/4109332495285652)，才终于算告一段落。我们非常荣幸能够发布全世界第一个专门面向国内用户的 PWA，但更荣幸的是能与 Google、UC 以及腾讯合作，一起推动国内 web 与浏览器生态的发展。\n\n## 多页应用、Vue、PWA？\n\n对于构建一个希望达到原生应用级别体验的 PWA，目前社区里的主流做法都是采用 SPA，即单页面应用模型（Single-page App）来组织整个 web 应用，业内最有名的几个 PWA 案例 [Twitter Lite][3]、 [Flipkart Lite][4]、[Housing Go][5] 与 [Polymer Shop][6] 无一例外。\n\n然而饿了么，与很多国内的电商网站一样，青睐多页面应用模型（MPA，Multi-page App）所能带来的一些好处，也因此在一年多将移动站从基于 Angular.js 的单页应用重构为目前的多页应用模型。团队最看重的优点莫过于页面与页面之间的隔离与解耦，这使得我们可以将每个页面当做一个独立的“微服务”来看待，这些服务可以被独立迭代，独立提供给各种第三方的入口嵌入，甚至被不同的团队独立维护。而整个网站则只是各种服务的集合而非一个巨大的整体。\n\n \n与此同时，我们仍然依赖 [Vue.js](http://vuejs.org/) 作为 JavaScript 框架。Vue 除了是 React/Angular 这种“重型武器”的竞争对手外，其轻量与高性能的优点使得它同样可以作为传统多页应用开发中流行的 “jQuery/Zepto/Kissy + 模板引擎” 技术栈的完美替代。Vue 提供的组件系统、声明式与响应式编程更是提升了代码组织、共享、数据流控制、渲染等各个环节的开发效率。[Vue 还是一个渐进式框架]((https://www.youtube.com/watch?v=pBBSp_iIiVM))，如果网站的复杂度继续提升，我们可以按需、增量地引入 Vuex 或 Vue-Router 这些模块。万一哪天又要改回单页呢？（谁知道呢……）\n\n2017 年，PWA 已经成为 web 应用新的风潮。我们决定试试，以我们现有的“Vue + 多页”的架构，能在升级 PWA 的道路上走多远，达到怎样的效果。\n\n\n## 实现 “PRPL” 模式\n\n[“PRPL”][7]（读作 “purple”）是 Google 的工程师提出的一种 web 应用架构模式，它旨在利用现代 web 平台的新技术以大幅优化移动 web 的性能与体验，对如何组织与设计高性能的 PWA 系统提供了一种高层次的抽象。我们并不准备从头重构我们的 web 应用，不过我们可以把实现 “PRPL” 模式作为我们的迁移目标。“PRPL”实际上是 Push/Preload、Render、Precache、Lazy-Load 的缩写，我们会在下文中展开它们的具体含义。\n\n\n### 1. PUSH/PRELOAD，推送/预加载初始 URL 路由所需的关键资源。\n\n无论是 HTTP2 Server Push 还是 `<link rel=\"preload\">`，其关键都在于，我们希望提前请求一些隐藏在应用依赖关系（Dependency Graph）较深处的资源，以节省 HTTP 往返、浏览器解析文档、或脚本执行的时间。比如说，对于一个基于路由进行 code splitting 的 SPA，如果我们可以在 webpack 清单、路由等入口代码（entry chunks）被下载与运行之前就把初始 URL，即用户访问的入口 URL 路由所依赖的代码用 Server Push 推送或 `<link rel=\"preload\">` 进行提前加载。那么当这些资源被真正请求时，它们可能已经下载好并存在在缓存中了，这样就加快了初始路由所有依赖的就绪。\n\n在多页应用中，每一个路由本来就只会请求这个路由所需要的资源，并且通常依赖也都比较扁平。饿了么移动站的大部分脚本依赖都是普通的 `<script>` 元素，因此他们可以在文档解析早期就被浏览器的 preloader 扫描出来并且开始请求，其效果其实与显式的 `<link rel=\"preload\">` 是一致的。\n\n![](/img/in-post/post-eleme-pwa/PUSH-link-rel-preload.jpg)\n\n我们还将所有关键的静态资源都伺服在同一域名下（不再做域名散列），以更好的利用 HTTP2 带来的多路复用（Multiplexing）。同时，我们也在进行着对 API 进行 Server Push 的[实验](https://zhuanlan.zhihu.com/p/26757514)。\n\n\n\n### 2. RENDER，渲染初始路由，尽快让应用可被交互\n\n既然所有初始路由的依赖都已经就绪，我们就可以尽快开始初始路由的渲染，这有助于提升应用诸如首次渲染时间、可交互时间等指标。多页应用并不使用基于 JavaScript 的路由，而是传统的 HTML 跳转机制，所以对于这一部分，多页应用其实不用额外做什么。\n\n\n### 3. PRE-CACHE，用 Service Worker 预缓存剩下的路由\n\n这一部分就需要 [Service Worker][9] 的参与了，Service Worker 是一个位于浏览器与网络之间的客户端代理，它以可拦截、处理、响应流经的 HTTP 请求，使得开发者得以从缓存中向 web 应用提供资源而闻名。不过，Service Worker 其实也可以主动发起 HTTP 请求，在“后台” 预请求与预缓存我们未来所需要的资源。\n\n![](/img/in-post/post-eleme-pwa/PRECACHE-future-routes.jpg)\n\n\n我们已经使用 [Webpack][10] 在构建过程中进行 `.vue` 编译、文件名哈希等工作，于是我们编写了一个 webpack 插件来帮助我们收集需要缓存的依赖到一个“预缓存清单”中，并使用这个清单在每次构建时生成新的 Service Worker 文件。在新的 Service Worker 被激活时，清单里的资源就会被请求与缓存，这其实与 [SW-Precache 这个库的运行机制][11]非常接近。\n\n**实际上，我们只对我们标记为“关键路由”的路由进行依赖收集。**你可以将这些“关键路由”的依赖理解为我们整个应用的 [\"App Shell\"][12] 或者说“安装包”。一旦它们都被缓存，或者说成功安装，无论用户是在线离线，我们的 web 应用都可以从缓存中直接启动。对于那些并不那么重要的路由，我们则采取在运行时增量缓存的方式。我们使用的 [SW-Toolbox][13] 提供了 LRU 替换策略与 TTL 失效机制，可以保证我们的应用不会超过浏览器的缓存配额。\n\n### 4. LAZY-LOAD 按需懒加载、懒实例化剩下的路由\n\n懒加载与懒实例化剩下的路由对于 SPA 是一件相对麻烦点儿的事情，你需要实现基于路由的 code splitting 与异步加载。幸运的是，这又是一件不需要多页应用担心的事情，多页应用中的各个路由天生就是分离的。\n\n值得说明的是，无论单页还是多页应用，如果在上一步中，我们已经将这些路由的资源都预先下载与缓存好了，那么懒加载就几乎是瞬时完成的了，这时候我们就只需要付出实例化的代价。\n\n---\n\n\n这四句话即是 PRPL 的全部了。有趣的是，我们发现多页应用在实现 PRPL 这件事甚至比单页还要容易一些\b。那么结果如何呢？\n\n![](/img/in-post/post-eleme-pwa/Lighthouse-before.png)\n\n根据 Google 推出的 Web 性能分析工具 Lighthouse（v1.6），在模拟的 3G 网络下，用户的初次访问（无任何缓存）大约在 2 秒左右达到“可交互”，可以说非常不错。而对于再次访问，由于所有资源都直接来自于 Service Worker 缓存，页面可以在 1 秒左右就达到可交互的状态了。\n\n但是，故事并不是这么简单得就结束了。在实际的体验中我们发现，**应用在页与页的切换时，仍然存在着非常明显的白屏空隙**，由于 PWA 是全屏运行的，白屏对用户体验所带来的负面影响甚至比以往在浏览器内更大。我们不是已经用 Service Worker 缓存了所有资源了吗，怎么还会这样呢？\n\n![](/img/in-post/post-eleme-pwa/before-skeleton.jpg)\n*从首页点击到发现页，跳转过程中的白屏*\n\n\n\n## 多页应用的陷阱：重启开销\n\n\n与 SPA 不同，在多页应用中，路由的切换是原生的浏览器文档跳转（Navigating across documents），这意味着之前的页面会被完全丢弃而浏览器需要为下一个路由的页面重新执行所有的启动步骤：重新下载资源、重新解析 HTML、重新运行 JavaScript、重新解码图片、重新布局页面、重新绘制……即使其中的很多步骤本是可以在多个路由之间复用的。这些工作无疑将产生巨大的计算开销，也因此需要付出相当的时间成本。\n\n图中为我们的入口页（同时也是最重的页面）在 2 倍 CPU 节流模拟下的 profile 数据。即使我们可以将“可交互时间”控制在 1 秒左右，我们的用户仍然会觉得这对于“仅仅切换个标签”来说实在是太慢了。\n\n![](/img/in-post/post-eleme-pwa/msite-Before-Optim.png)\n\n### 巨大的 JavaScript 重启开销\n\n根据 Profile，我们发现在首次渲染（First Paint）发生之前，大量的时间（900 毫秒）都消耗在了 JavaScript 的运行上（Evaluate Script）。几乎所有脚本都是阻塞的（Parser-blocking），不过因为所有的 UI 都是由 JavaScript/Vue 驱动的，倒也不会有性能影响。这 900ms 中，约一半是消耗在包括 Vue 运行时、组件、库等依赖的运行上，而另一半则花在了业务组件实例化时 Vue 的启动与渲染上。从软件工程角度来说，我们需要这些抽象，所以这里并不是想责怪 JavaScript 或是 Vue 所带来的开销。\n\n**但是，在 SPA 中，JavaScript 的启动成本是均摊到整个生命周期的：** 每个脚本都只需要被解析与编译一次，诸如生成 Virtual DOM 等较重的任务可以只执行一次，像 Vue 的 ViewModel 或是 Virtual DOM 这样的大对象也可以被留在内存里复用。**可惜在多页应用里就不是这样了，我们每次切换页面都为 JavaScript 付出了巨大的重启代价。**\n\n### 浏览器的缓存啊，能不能帮帮忙？\n\n能，也不能。\n\nV8 提供了[代码缓存（code caching）](http://v8project.blogspot.com/2015/07/code-caching.html)，可以将编译后的机器码在本地拷贝一份，这样我们就可以在下次请求同一个脚本时一次省略掉请求、解析、编译的所有工作。而且，对于缓存在 Service Worker 配套的 Cache Storage 中的脚本，会在第一次执行后就触发 V8 的代码缓存，这对于我们的多页切换能提供不少帮助。\n\n另外一个你或许听过的浏览器缓存叫做“进退缓存”，Back-Forward Cache，简称 bfcache。浏览器厂商对其的命名各异，Opera 称之为 Fast History Navigation，Webkit 称其为 Page Cache。但是思路都一样，**就是我们可以让浏览器在跳转时把前一页留存在内存中，保留 JavaScript 与 DOM 的状态，而不是全都销毁掉。**你可以随便找个传统的多页网站在 iOS Safari 上试试，无论是通过浏览器的前进后退按钮、手势，还是通过超链接（会有一些不同），基本都可以看到瞬间加载的效果。\n\nBfcache 其实非常适合多页应用。但不幸的是，Chrome 由于内存开销与其多进程架构等原因目前并不支持。Chrome 现阶段仅仅只是用了传统的 HTTP 磁盘缓存，来稍稍简化了一下加载过程而已。对于 Chromium 内核霸占的 Android 生态来说，我们没法指望了。\n\n\n## 为“感知体验”奋斗\n\n尽管多页应用面临着现实中的不少性能问题，我们并不想这么快就妥协。一方面，我们尝试尽可能减少在页面达到可交互时间前的代码执行量，比如减少/推迟一些依赖脚本的执行，还有减少初次渲染的 DOM 节点数以节省 Virtual DOM 的初始化开销。另一方面，我们也意识到应用在感知体验上还有更多的优化空间。\n\nChrome 产品经理 Owen 写过一篇 [Reactive Web Design: The secret to building web apps that feel amazing](https://medium.com/@owencm/reactive-web-design-the-secret-to-building-web-apps-that-feel-amazing-b5cbfe9b7c50)，谈到两种改进感知体验的手段：一是使用骨架屏（Skeleton Screen）来实现瞬间加载；二是预先定义好元素的尺寸来保证加载的稳定。跟我们的做法可以说不谋而合。\n\n为了消除白屏时间，我们同样引入了尺寸稳定的骨架屏来帮助我们实现瞬间的加载与占位。即使是在硬件很弱的设备上，我们也可以在点击切换标签后立刻渲染出目标路由的骨架屏，以保证 UI 是稳定、连续、有响应的。我录了[两个](https://youtu.be/K5JBGnMYO1s)[视频](https://youtu.be/w1ZbNsHmRjs)放在 Youtube 上，不过如果你是国内读者，你可以直接访问饿了么移动网站来体验实地的效果 ;) 最终效果如下图所示。\n\n![](/img/in-post/post-eleme-pwa/after-skeleton.jpg)\n*在添加骨架屏后，从发现页点回首页的效果*\n\n这效果本该很轻松的就能实现，不过实际上我们还费了点功夫。\n\n\n### 在构建时使用 Vue 预渲染骨架屏\n\n你可能已经想到了，为了让骨架屏可以被 Service Worker 缓存，瞬间加载并独立于 JavaScript 渲染，我们需要把组成骨架屏的 HTML 标签、CSS 样式与图片资源一并内联至各个路由的静态 `*.html` 文件中。\n\n不过，我们并不准备手动编写这些骨架屏。你想啊，如果每次真实组件有迭代（每一个路由对我们来说都是一个 Vue 组件）我们都需要手动去同步每一个变化到骨架屏的话，那实在是太繁琐且难以维护了。好在，[骨架屏不过是当数据还未加载进来前，页面的一个空白版本而已](https://www.lukew.com/ff/entry.asp?1797)。如果我们能将骨架屏实现为真实组件的一个特殊状态 —— “空状态”的话，我们理论上就可以从真实组件中直接渲染出骨架屏来。\n\n而 Vue 的多才多艺就在这时体现出来了，我们真的可以用 [Vue.js 的服务端渲染模块](https://ssr.vuejs.org/en/) 来实现这个想法，不过不是用在真正的服务器上，而是在构建时用它把组件的空状态预先渲染成字符串并注入到 HTML 模板中。你需要调整你的 Vue 组件代码使得它可以在 Node 上执行，有些页面对 DOM/BOM 的依赖一时无法轻易去除得，我们目前只好额外编写一个 `*.shell.vue` 来暂时绕过这个问题。\n\n\n### 关于浏览器的绘制（Painting）\n\nHTML 文件中有标签并不意味着这些标签就能立刻被绘制到屏幕上，你必须保证页面的[关键渲染路径](https://developers.google.com/web/fundamentals/performance/critical-rendering-path/)是为此优化的。很多开发者相信将 script 标签放在 body 的底部就足以保证内容能在脚本执行之前被绘制，这对于能渲染不完整 DOM 树的浏览器（比如桌面浏览器常见的流式渲染）来说可能是成立的。但移动端的浏览器很可能因为考虑到较慢的硬件、电量消耗等因素并不这么做。**不仅如此，即使你曾被告知设为 `async` 或 `defer` 的脚本就不会阻塞 HTML 解析了，但这可不意味着浏览器就一定会在执行它们之前进行渲染。**\n\n![](https://html.spec.whatwg.org/images/asyncdefer.svg)\n\n首先我想澄清的是，根据 [HTML 规范 Scripting 章节](https://html.spec.whatwg.org/multipage/scripting.html)，`async` 脚本是在其请求完成后立刻运行的，因此它本来就可能阻塞到解析。只有 `defer`（且非内联）与最新的 `type=module` 被指定为“一定不会阻塞解析”。（不过 `defer` 目前也有点小问题……我们稍后会再提到）\n\n**而更重要的是，一个不阻塞 HTML 解析的脚本仍然可能阻塞到绘制。**我做了一个简化的**“最小多页 PWA”**（Minimal Multi-page PWA，或 MMPWA）来测试这个问题，：我们在一个 `async`（且确实不阻塞 HTML 解析）脚本中，生成并渲染 1000 个列表项，然后测试骨架屏能否在脚本执行之前渲染出来。下面是通过 USB Debugging 在我的 Nexus 5 真机上录制的 profile：\n\n![](/img/in-post/post-eleme-pwa/thisTick-&-Load.png)\n\n\n是的，出乎意料吗？首次渲染确实被阻塞到脚本执行结束后才发生。究其原因，**如果我们在浏览器还未完成上一次绘制工作之前就过快得进行了 DOM 操作，我们亲爱的浏览器就只好抛弃所有它已经完成的像素，且一直要等待到 DOM 操作引起的所有工作结束之后才能重新进行下一次渲染。**而这种情况更容易在拥有较慢 CPU/GPU 的移动设备上出现。\n\n\n### 黑魔法：利用 setTimeout() 让绘制提前\n\n不难发现，骨架屏的绘制与脚本执行实际是一个竞态。大概是 Vue 太快了，我们的骨架屏还是有非常大的概率绘制不出来。于是我们想着如何能让脚本执行慢点，或者说，“懒”点。于是我们想到了一个经典的 Hack： `setTimeout(callback, 0)`。我们试着把 MMPWA 中的 DOM 操作（渲染 1000 个列表）放进 `setTimeout(callback, 0)` 里……\n\n![](/img/in-post/post-eleme-pwa/nextTick-&-Load.png)\n\n当当！首次渲染瞬间就被提前了。如果你熟悉浏览器的**事件循环模型（event loop）**的话，这招 Hack 其实是通过 setTimeout 的回调把 DOM 操作放到了事件循环的任务队列中以避免它在当前循环执行，这样浏览器就得以在主线程空闲时喘息一下（更新一下渲染）了。如果你想亲手试试 MMPWA 的话，你可以访问 [github.com/Huxpro/mmpwa](https://github.com/Huxpro/mmpwa) 或 [huangxuan.me/mmpwa/](https://huangxuan.me/mmpwa) 访问代码与 Demo。我把 UI 设计为了 A/B Test 的形式并改为渲染 5000 个列表项来让效果更夸张一些。\n\n回到饿了么 PWA 上，我们同样试着把 `new Vue()` 放到了 `setTimeout` 中。果然，黑魔法再次显灵，骨架屏在每次跳转后都能立刻被渲染。这时的 Profile 看起来是这样的：\n\n![](/img/in-post/post-eleme-pwa/msite-After-Optim.png)\n\n现在，我们在 400ms 时触发首次渲染（骨架屏），在 600ms 时完成真实 UI 的渲染并达到页面的可交互。你可以拉上去详细对比下优化前后 profile 的区别。\n\n\n### 被我 “defer” 的有关 `defer` 的 Bug\n\n不知道你发现没有，在上图的 Profile 中，我们仍然有不少脚本是阻塞了 HTML 解析的。好吧让我解释一下，由于历史原因，我们确实保留了一部分的阻塞脚本，比如侵入性很强的 [lib-flexible](https://github.com/amfe/lib-flexible)，我们没法轻易去除它。不过，profile 里的大部分阻塞脚本实际上都设置了 `defer`，我们本以为他们应该在 HTML 解析完成之后才被执行，结果被 profile 打了一脸。\n\n我和 [Jake Archibald](https://twitter.com/jaffathecake) [聊了一下](https://twitter.com/Huxpro/status/859842124849827841)，果然这是 Chrome 的 Bug：`defer` 的脚本被完全缓存时，并没有遵守规范等待解析结束，反而阻塞了解析与渲染。Jake 已经提交在 [crbug](https://bugs.chromium.org/p/chromium/issues/detail?id=717979) 上了，一起给它投票吧~\n\n最后，是优化后的 Lighthouse 跑分结果，同样可以看到明显的性能提升。需要说明的是，能影响 Lighthouse 跑分的因素有很多，所以我建议你以控制变量（跑分用的设备、跑分时的网络环境等）的方式来进行对照实验。\n\n![](/img/in-post/post-eleme-pwa/Lighthouse-after.png)\n\n最后附上一张图，这张图当时是做给 Addy Osmani 的 I/O 演讲用的，描述了饿了么 PWA 是如何结合 Vue 来实现多页应用的 PRPL 模式，可以作为一个架构的参考与示意图。\n\n![](/img/in-post/post-eleme-pwa/Architecture.png)\n\n\n## 一些感想\n\n### 多页应用仍然有很长的路要走\n\nWeb 是一个极其多样化的平台。从静态的博客，到电商网站，再到桌面级的生产力软件，它们全都是 Web 这个大家庭的第一公民。而我们组织 web 应用的方式，也同样只会更多而不会更少：多页、单页、Universal JavaScript 应用、WebGL、以及可以预见的 Web Assembly。不同的技术之间没有贵贱，但是适用场景的差距确是客观存在的。\n\n[Jake](https://twitter.com/jaffathecake) 曾在 [Chrome Dev Summit 2016](https://youtu.be/J2dOTKBoTL4?list=PLNYkxOF6rcIBTs2KPy1E6tIYaWoFcG3uj) 上说过 \"PWA !== SPA\"。可是尽管我们已经用上了一系列最新的技术（PRPL、Service Worker、App Shell……），我们仍然因为多页应用模型本身的缺陷有着难以逾越的一些障碍。多页应用在未来可能会有“bfcache API”、Navigation Transition 等新的规范以缩小跟 SPA 的距离，不过我们也必须承认，时至今日，多页应用的局限性也是非常明显的。\n\n\n### 而 PWA 终将带领 web 应用进入新的时代\n\n即使我们的多页应用在升级 PWA 的路上不如单页的那些来得那么闪亮，但是 PWA 背后的想法与技术却实实在在的帮助我们在 web 平台上提供了更好的用户体验。\n\nPWA 作为[下一代 Web 应用模型](https://zhuanlan.zhihu.com/p/25167289)，其尝试解决的是 web 平台本身的根本性问题：对网络与浏览器 UI 的硬依赖。因此，任何 web 应用都可以从中获益，这与你是多页还是单页、面向桌面还是移动端、是用 React 还是 Vue 无关。或许，它还终将改变用户对移动 web 的期待。现如今，谁还觉得桌面端的 web 只是个看文档的地方呢？\n\n还是那句老话：让我们的用户，也像我们这般热爱 web 吧。\n\n---\n\n最后，感谢饿了么的王亦斯、任光辉、题叶，Google 的 Michael Yeung、DevRel 团队， UC 浏览器团队，腾讯 X5 浏览器团队在这次项目中的合作。感谢尤雨溪、陈蒙迪和 Jake Archibald 在写作过程中给予我的帮助。\n\n\n\n[1]: https://twitter.com/vuejs/status/834087199008239619\n[2]: https://developers.google.com/web/progressive-web-apps/\n[3]: https://blog.twitter.com/2017/how-we-built-twitter-lite\n[4]: https://medium.com/progressive-web-apps/building-flipkart-lite-a-progressive-web-app-2c211e641883\n[5]: https://medium.com/engineering-housing/progressing-mobile-web-fac3efb8b454\n[6]: https://shop.polymer-project.org/\n[7]: https://developers.google.com/web/fundamentals/performance/prpl-pattern/\n[8]: https://calendar.perfplanet.com/2013/big-bad-preloader/\n[9]: https://w3c.github.io/ServiceWorker/v1/\n[10]: https://webpack.github.io/\n[11]: https://medium.com/@Huxpro/how-does-sw-precache-works-2d99c3d3c725\n[12]: https://developers.google.com/web/updates/2015/11/app-shell\n[13]: https://googlechrome.github.io/sw-toolbox/\n\n"
  },
  {
    "path": "_includes/search.html",
    "content": "<!-- Search -->\n<div class=\"search-page\">\n  <div class=\"search-icon-close-container\">\n    <span class=\"search-icon-close\">\n      <i class=\"fa fa-chevron-down\"></i>\n    </span>\n  </div>\n  <div class=\"search-main container\">\n    <div class=\"row\">\n      <div class=\"col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1\">\n        <form></form>\n        <input type=\"text\" id=\"search-input\" placeholder=\"$ grep...\">\n        </form>\n        <div id=\"search-results\" class=\"mini-post-list\"></div>\n      </div>\n    </div>\n  </div>\n</div>"
  },
  {
    "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    {% include search.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  <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\n      postlist-container\">\n      {{ content }}\n    </div>\n    <!-- Sidebar Container -->\n    <div class=\"col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1\n      sidebar-container\">\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    <div class=\"col-lg-8 col-lg-offset-1 col-md-8 col-md-offset-1 col-sm-12\n      col-xs-12 postlist-container\">\n      {{ content }}\n    </div>\n    <!-- Sidebar Container -->\n    <div class=\"col-lg-3 col-lg-offset-0 col-md-3 col-md-offset-0 col-sm-12\n      col-xs-12 sidebar-container\">\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\n      <!-- Ads -->\n      {% include ads.html %}\n    </div>\n    {% endif %}\n  </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\"></script>\n{% endif %}\n"
  },
  {
    "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                    {% include multilingual-sel.html %}\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        {% unless page.no-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        {% endunless %}\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: 'hover',\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/2014-01-29-hello-2015.markdown",
    "content": "---\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\"\ncatalog: true\ntags:\n    - Meta\n---\n\n> “Yeah It's on. ”\n\n\nHux 的 Blog 就这么开通了。\n\n[跳过废话，直接看技术实现 ](#build) \n\n2015 年，Hux 总算有个地方可以好好写点东西了。\n\n\n作为一个程序员， Blog 这种轮子要是挂在大众博客程序上就太没意思了。一是觉得大部分 Blog 服务都太丑，二是觉得不能随便定制不好玩。之前因为太懒没有折腾，结果就一直连个写 Blog 的地儿都没有。\n\n在玩了一段时间知乎之后，答题的快感又激起了我开博客的冲动。之前的[个人网站](http://huangxuan.me/portfolio)是作品集形式的（现在集成进来了），并不适合用来写博文，一不做二不休，花一天搞一个吧！\n\n\n<p id = \"build\"></p>\n\n## 正文\n\n接下来说说搭建这个博客的技术细节。  \n\n正好之前就有关注过 [GitHub Pages](https://pages.github.com/) + [Jekyll](http://jekyllrb.com/) 快速 Building Blog 的技术方案，非常轻松时尚。\n\n其优点非常明显：\n\n* **Markdown** 带来的优雅写作体验\n* 非常熟悉的 Git workflow ，**Git Commit 即 Blog Post**\n* 利用 GitHub Pages 的域名和免费无限空间，不用自己折腾主机\n\t* 如果需要自定义域名，也只需要简单改改 DNS 加个 CNAME 就好了 \n* Jekyll 的自定制非常容易，基本就是个模版引擎\n\n\n本来觉得最大的缺点可能是 GitHub 在国内访问起来太慢，所以第二天一起床就到 GitCafe(Chinese GitHub Copy，现在被 Coding 收购了) 迁移了一个[镜像](http://huxpro.coding.me)出来，结果还是巨慢。\n\n哥哥可是个前端好嘛！ 果断开 Chrome DevTool 查了下网络请求，原来是 **pending 在了 Google Fonts** 上，页面渲染一直被阻塞到请求超时为止，难怪这么慢。  \n忍痛割爱，只好把 Web Fonts 去了（反正超时看到的也只能是 fallback ），果然一下就正常了，而且 GitHub 和 GitCafe 对比并没有感受到明显的速度差异，虽然 github 的 ping 值明显要高一些，达到了 300ms，于是用 DNSPOD 优化了一下速度。\n\n\n---\n\n配置的过程中也没遇到什么坑，基本就是 Git 的流程，相当顺手\n\n大的 Jekyll 主题上直接 fork 了 Clean Blog（这个主题也相当有名，就不多赘述了。唯一的缺点大概就是没有标签支持，于是我给它补上了。）\n\n本地调试环境需要 `gem install jekyll`，结果 rubygem 的源居然被墙了……后来手动改成了我大淘宝的镜像源才成功\n\nTheme 的 CSS 是基于 Bootstrap 定制的，看得不爽的地方直接在 Less 里改就好了（平时更习惯 SCSS 些），**不过其实我一直觉得 Bootstrap 在移动端的体验做得相当一般，比我在淘宝参与的团队 CSS 框架差多了……**所以为了体验，也补了不少 CSS 进去\n\n最后就进入了耗时反而最长的**做图、写字**阶段，也算是进入了**写博客**的正轨，因为是类似 Hack Day 的方式去搭这个站的，所以折腾折腾着大半夜就过去了。\n\n第二天考虑中文字体的渲染，fork 了 [Type is Beautiful](http://www.typeisbeautiful.com/) 的 `font` CSS，调整了字号，适配了 Win 的渣渲染，中英文混排效果好多了。\n\n\n## 后记\n\n回顾这个博客的诞生，纯粹是出于个人兴趣。在知乎相关问题上回答并获得一定的 star 后，我决定把这个博客主题当作一个小小的开源项目来维护。\n\n在经历 v1.0 - v1.5 的蜕变后，这个博客主题愈发完整，不但增加了诸多 UI 层的优化（opinionated）；在代码层面，更加丰富的配置项也使得这个主题拥有了更好的灵活性与可拓展性。而作为一个开源项目，我也积极的为其完善文档与解决 issue。\n\n如果你恰好逛到了这里，希望你也能喜欢这个博客主题。\n\n—— Hux 后记于 2015.10\n"
  },
  {
    "path": "_posts/2014-08-16-miui6.markdown",
    "content": "---\nlayout:     post\ntitle:      \"如何评价 MIUI 6？\"\ndate:       2014-08-16 12:00:00\nauthor:     \"Hux\"\nheader-img: \"img/post-bg-miui6.jpg\"\ntags:\n    - 知乎\n    - 产品\n    - UX/UI\n---\n\n> 这篇文章转载自[我在知乎上的回答](http://www.zhihu.com/question/24783844/answer/29286896)\n\n\n<div>\n    <blockquote>MIUI 6，充满了“借鉴”，iOS 7 版的 Android……\n        <br>米 4，碉堡了，不服跑个分，简直就是 iPhone 4……</blockquote>你们说得这些我一点都不反对。\n    <br>\n    <br><b>可是，你们对小米的要求太高了</b>。\n    <br>\n    <br>其实小米说到底也不过是一个才初创4年的公司而已，\n    <br><b>你是指望小米能引领一套新的设计风格？</b>\n    <br><b>还是指望它能在国际上体现一下我国的自主创新能力？</b>\n    <br>\n    <br>你想太多了。\n    <br>\n    <br>更何况，<b>MIUI也不是没有设计</b>，它比很多国内，国际大厂的ROM好看好用太多了。\n    <br>它只是没有多少新设计而已， iOS 7 的视觉，混着大部分 Android ＋ WP 的交互。也不知道是因为确实欣赏 Android 的一些交互，还是因为毕竟是基于 Android 懒得改了。\n    <br>\n    <br><b>因为没有一个背后的设计思想在支撑，于是它就把所有自己觉得好，觉得会被认可的东西抄过来了而已。</b>\n    <br>\n    <br><b>这思路一点问题都没有，</b><b>大部分用户一定会觉得更好看了</b>，国际范儿又有设计感。最多是少数圈内人士（包括我），那群也不真正买它手机用的人，在那愤愤不平而已。\n    <br>\n    <br><b>自立门派风险太大了。</b>\n    <br><b>MI 4 的配置 + MIUI 6，在这个价位几乎是无敌的，这就够了。</b>\n    <br>\n    <br>至于官方说的什么“糖果式”设计，那简直就是笑话。跟 Ive 的 iOS 7 或是 Material Design，Metro 所设计之设计，完全不在一个高度上。\n    <br>\n    <br>\n    <br>其实有的时候觉得小米很像腾讯（尤其是更早些年的腾讯）。\n    <br><b>其实本来也就不是什么创新者的角色，那就做借鉴和整合呗。</b>\n    <br>\n    <br>用户喜欢什么，\n    <br>公司需要什么，\n    <br>大众流行什么，\n    <br>那我们就做呗。\n    <br>\n    <br><b>拿下市场才是第一位的，不出错才是第一位的</b>。\n    <br><b>先做大了才有可能去做更大的事啊</b>。\n    <br>\n    <br>老罗再有情怀，锤子要是死了，那也就这么死了。\n    <br>\n    <br>你指责小米没有多少创新，或是腾讯老是山寨 start up ，我同意，我陪你愤愤不平，可是又有什么意思呢。\n    <br>\n    <br>它们这么做，对现有公司发展来说，\n    <br><b>简直是一点错都没有。</b>\n    <br>\n    <br>\n</div>\n"
  },
  {
    "path": "_posts/2014-09-04-is-pure-android-better.markdown",
    "content": "---\nlayout:     post\ntitle:      \"对中国用户而言，Pure Android 是否比 MIUI 或 Flyme 体验更好？\"\nsubtitle:   \"\"\ndate:       2014-09-04 12:00:00\nauthor:     \"Hux\"\nheader-img: \"img/post-bg-android.jpg\"\ntags:\n    - 知乎\n    - 产品\n    - UX/UI\n---\n\n> 这篇文章转载自[我在知乎上的回答](http://www.zhihu.com/question/25104721/answer/30108886)\n\n\n<p>哎呀～不要站队嘛。其实这是一个很有意思的题目，让我们一点点来看\n    <br>\n    <br>哦对，谢妖～。本人是Nexus 5用户，系统当然是Pure Android KitKat啦（臭谷粉！点Down！喂喂喂我还没给结论呢）\n    <br><b>毕竟是回答问题嘛，先给一个明确的答案</b>：\n    <br>\n    <br><b>否。（</b><b>对中国用户而言，Pure Android 并不比 MIUI 或 Flyme 体验更好。</b><b>）</b>\n\n    <p>从下面「 居然比关注数还多」的回答中，就可以看出大家都是急于站队的样子：</p>\n\n    <ul>\n        <li>Google Service！翻墙很轻松好吗！Geek站过来，有品味绝逼原生阿。</li>\n        <li>没用过Pure，国内Google能用！？本地化多重要，易用果断MIUI/Flyme 啊！（咦 米粉和魅粉居然在一致对外上达成了共识）</li>\n    </ul>\n    <br>从答案我们也可以看出，中国用户的确是一个过于复杂的群体，那这个问题怎么办？\n    <br>\n    <br><b>数学老师教过哒，分类讨论啊！</b>\n    <br>（来，开始认真了。注意，我只分两类，数量非常小的Geek用户，和其余都算在内的非Geek用户）\n    <br>\n    <br>\n\n    <p>先说好理解的：</p>\n\n    <ul>\n        <li><b>为什么 Geek 用户 都爱使用Pure Android？：</b>\n        </li>\n    </ul>在国内，使用Pure Android其实是有很多障碍的：众所周知Google基本被墙死，去年还能上上的G+，Gmail 最近基本报废，回国后Google Now不开VPN永远都是Sign error或者No internet connection……那干嘛还用？\n    <br>\n    <br><b>因为这群人是Geek呀！</b>这群谷粉、安卓粉、IT科技粉、设计师、工程师们，这群充满技术情节的人儿们，为了我们的品味（逼格），挂着VPN，连着美版的Play Store，用着Android/Material Design 的 GMS，Chrome Beta，FB，Twitter，WhatsApp……就这么一路高歌的走下去了。\n    <br>\n    <br>你看！Action Bar ＋ Navigation Drawer 多好用！\n    <br>你看！Fixed Tabs 可以滑的好吗！\n    <br>你看！流畅不！ART开起来妥妥的流畅度爆iOS！\n    <br>你看！原生Android 哪里会越用用卡！？你升4.4.4了吗 ？\n    <br>\n    <br><b>哪里要担心这群人啊。</b>国内买不到的Nexus，用不了GMS，这都不叫事。\n    <br>\n    <br>\n\n    <p>那么，</p>\n\n    <ul>\n        <li><b>为什么 非Geek 用户 不适合使用Pure Android？：</b>\n        </li>\n    </ul>GMS的问题就不多说了，妥妥是用不了，在VPN之间切换也是麻烦。\n    <br>也不说Pure Android不那么好刷到的问题（当然你可以刷CM），\n    <br>我们就直接来看最核心的问题：\n    <br>\n    <br><b>「 Pure Android 的交互设计真的比 MIUI / Flyme 好吗？」<br></b>\n    <br><b>不见得。</b>\n    <br><b>所谓设计，第一个要考虑的就是目标用户。</b>\n    <br>\n    <br>为什么Pure Android的交互设计让Geek觉得用户体验好？\n    <br>\n    <ul>\n        <li>国外规范的 Android Design 生态环境打造统一的 Pure Android 体验</li>\n        <li>更高级的手势/App运用带来了很多便利（典型的例子SwipePad）</li>\n        <li>有着工程师思想的他们可以轻易理解Android的复杂逻辑</li>\n        <li>有着工程师思想的他们总能自己轻松躲开一些设计问题</li>\n    </ul>\n    <br>而 Pure Android 之于 普通用户 呢？\n    <br><b>「 这些优势基本荡然无存」</b>，反而，混乱的国内生态环境带来大部分中国用户对Android Design的陌生，相比iOS复杂许多的Android逻辑带来较高的学习成本……\n    <br>\n    <br>而MIUI/Flyme在设计方面上的本地化，主要就是出来解决这个问题的。\n    <br>我们可以看到，其实MIUI/Flyme做得大部分工作，除了视觉外，就是<b>简化信息层级，降低交互学习成本，遮住Android系统过于复杂的部分，在易用性上向iOS靠拢</b>。\n    <br>\n    <br>如果说在这里MIUI/Flyme还只能和Pure Android 打个平手的话……\n    <br>\n    <br><b>MIUI 和 Flyme 的本地化还远没有完：</b>\n    <br>\n    <br>你在国内总要用国内的互联网服务吧？\n    <br><b>集成，</b>我全给你全整合进来，打造一条龙服务\n    <br>\n    <ul>\n        <li>应用商店\n            <br>\n        </li>\n        <li>云存储/云服务（自己提供或合作）\n            <br>\n        </li>\n        <li>数字娱乐消费（音乐/游戏/阅读/视频/主题/壁纸/铃声……）\n            <br>\n        </li>\n        <li>安全（小白最爱用的系统清理，陌生号码拦截……）\n            <br>\n        </li>\n        <li>生活服务（支付，地图，快递，订餐，打车，旅游……）\n            <br>\n        </li>\n        <li>社交（美图，快速分享……）\n            <br>\n        </li>\n        <li>太多了。总之就是你想要什么有什么，自己没有就跟大家合作呗。\n            <br>\n        </li>\n    </ul><b>不够酷？</b>对大部分用户来说够酷了\n    <br>\n    <ul>\n        <li>小米，平板，盒子，电视，路由……MIUI的多屏体验</li>\n        <li>魅族，联合智能硬件，手表飞机插座……Connect to Meizu</li>\n    </ul><b>渠道成本低（不是指价格）</b>。这个其实也相当重要\n    <br>\n    <ul>\n        <li>容易刷到，适配机子广，稳定。</li>\n        <li>国内买得到，线下甚至有体验店，可以教你用呀什么的。</li>\n    </ul>\n    <br>更何况，对于大部分非Geek用户，手机虽不再只是当年的通讯工具那么简单，但充其量也就是一个智能电子设备而已。<b>能方便快速的享受到国内主流的互联网应用与服务，完成日常的需求就足以</b>。\n    <br>\n    <br><b>MIUI/Flyme 在这方面上的成绩，是Pure Android远不能比的。</b>\n    <br>\n    <br>所以我的结论是：\n    <br>\n    <br><b>对中国用户而言，Pure Android 并不比 MIUI 或 Flyme 体验更好。</b>\n    <br><b>对大部分中国用户而言，MIUI 或 Flyme 比 </b><b>Pure Android 的</b><b>体验更好。</b>\n    <br>\n    <br>\n    <br>\n    <br>\n    <br>没啥利益相关，我又不是云OS的\n</p>\n"
  },
  {
    "path": "_posts/2014-10-01-why-alibaba-ux-sucks.markdown",
    "content": "---\nlayout:     post\ntitle:      \"为什么阿里系软件体验都不好？\"\nsubtitle:   \"或许这就是所谓的企业 DNA \"\ndate:       2014-10-1 12:00:00\nauthor:     \"Hux\"\nheader-img: \"img/post-bg-alibaba.jpg\"\ntags:\n    - 知乎\n    - 产品\n    - 阿里\n---\n\n> 这篇文章转载自[我在知乎上的回答](http://www.zhihu.com/question/25657351/answer/31278511)\n\n\n<div >\n    <br>\n    <br><b>一言以蔽之，优先级。</b>\n    <br>这个优先级并不是由谁或者哪个Boss定的，而是<b>长期的市场竞争和业务需求下的结果</b>\n    <br>\n    <br>\n    <ul>\n        <li><b>为什么企鹅家的App用户体验较好？</b>\n        </li>\n    </ul>\n    企鹅家的主力产品，QQ、微信、QQ音乐、QQ空间 等，多是IM（即时通讯）、SNS（社交网络）、数字娱乐 等形态的产品。\n    <br>\n    <br><b>这类产品往往必须「直接依靠优秀的产品服务与用户体验」来赢得用户。</b>\n    <br>\n    <br>如果这点做不好，产品就无法在竞争中脱颖而出。这也使得在企鹅内部，<b>围绕这部分的要求，需求，反馈 </b><b>都一定最多，使得企鹅不得不把这部分做好</b>。\n    <br>\n    <br>\n    <ul>\n        <li><b>那为什么阿里系的App用户体验较差？</b>\n        </li>\n    </ul>\n    阿里系的主力产品，从1688、淘宝、再到支付宝、天猫、淘宝旅行、淘点点、一淘、旺旺，要么是电商类产品，要么就是电商类的延伸产品。\n    <br>\n    <br>而这类产品的核心竞争力（或者说要做好的难处），往往在<b>「如何与实体经济，甚至政府 打交道」、</b><b>「如何做好运营」，</b>而非优秀的用户体验。\n    <br>\n    <br>应该说，阿里从来都不是不重视用户体验，这两年更是愈发重视。但是因为身处这样的市场环境，<b>阿里必须先完成这些优先级更高的需求（海量的业务，运营需求）以抢占市场，</b>\n    <br>这才导致阿里内部无法有太多精力focus到客户端体验上。\n    <br>\n    <br>\n    <br>\n    <br>上面就算基本回答了题主的问题，\n    <br>不过，知乎惯例，多说几句：\n    <br>\n    <br><b>其实，上面的答案，也可以说这都是说辞。</b>\n    <br>\n    <br>在我刚刚加入阿里的时候，我也一度纳闷甚至郁闷这个事。直到我开始接触更多的项目，我才能逐渐理解「为什么会这样」。\n    <br>\n    <br><b>但是，这并不足以成为借口。</b>\n    <br><b>该不该改？ 当然该改。</b>\n    <br>\n    <br>我相信几乎所有阿里人，尤其UED，肯定都不希望这样。\n    <br>只能说，这需要阿里投入更多的人、更多的时间、更多的努力来做好\n    <br>\n    <br>\n    <br>\n    <br>以上。\n    <br>\n    <br>利益相关：\n    <br>阿里员工\n    <br>\n    <br>\n</div>\n"
  },
  {
    "path": "_posts/2014-11-20-responsive-web-design.markdown",
    "content": "---\nlayout:     post\ntitle:      \"你们觉得响应式好呢，还是手机和PC端分开来写？\"\ndate:       2014-11-20 12:00:00\nauthor:     \"Hux\"\nheader-img: \"img/post-bg-rwd.jpg\"\ntags:\n    - 知乎\n    - Web\n---\n\n> 这篇文章转载自[我在知乎上的回答](http://www.zhihu.com/question/25836425/answer/31564174)\n\n\n<div>\n\t<p>\n\t\t<b>根据你的产品特点，进行两种不同的设计，</b>\n\t    <br><b>根据你的设计需求，选择合适的技术方案</b>。\n    </p>\n    <br><b>A与B不是硬币的正反面，它们为了解决同一个问题而生，它们是同一种思想的延伸。</b>\n    <br>\n    <br>\n    <blockquote>移动和桌面设计的差别远不止是布局问题。只要有足够的编程量，这些差别是可以通过响应式设计来解决的。事实上，你可以认为如果一种设计不能兼顾两种平台的主要差别，就不能算是合格的响应式设计。但是，如果确实想要处理好平台间的所有差异，我们就回到了原点：进行两种不同的设计。\n        <br>\n        <br>——《Mobile Usability》（《贴心设计 打造高可用性的移动产品》）</blockquote>\n    <br>\n    <br>其实无论是什么解决方案，我们先来看看我们想要解决的问题：\n    <br>\n    <br><b>“屏幕尺寸越来越多，不同设备的交互特质也有着巨大的差别，我们希望我们的网站能够在移动手机、平板、桌面电脑，在键鼠、触摸、无障碍设备上都有优秀的用户体验。所以，我们需要网站的用户界面在不同的平台上有所不同。”</b>\n    <br>\n    <br>\n    <br>那怎么做呢，一个解决方案应运而生：\n    <br>\n    <br>\n    <ul>\n        <li><b>响应式设计 (Responsive Web design)</b>\n        </li>\n    </ul><b>狭义上</b>，我们把<b>主要依靠前端 CSS</b> （包括 Media Query 媒体查询，百分比流式布局，网格与Typography系统……）来对各种屏幕尺寸进行响应的做法，称之为响应式布局，又称作自适应网页设计，或者弹性设计。\n    <br>\n    <br>这种主要依靠CSS的方案有很多优点，比如：\n    <br>\n    <ul>\n        <ul>\n            <li>设计元素很容易被复用，设计成本低</li>\n            <li>前端只需要维护一套CSS代码，<b>维护成本</b>低</li>\n            <li>桌面端与移动端的设计十分接近，令用户感到“熟悉”</li>\n            <li>不需要任何服务器端的支持</li>\n            <li>与业务耦合程度低，复用程度高（ 以至于 Bootstrap、Foundation 等一干框架都跟进了这个解决方案 ）</li>\n        </ul>\n    </ul>但问题也很明显，比如：\n    <br>\n    <ul>\n        <ul>\n            <li>设计需求复杂时，前端的<b>开发成本</b>没有任何减轻</li>\n            <li>无论是针对桌面还是移动的CSS代码（甚至图片资源文件）都会被同等的下载到客户端（<b>没有考虑移动端的网络优化</b>）</li>\n            <li>如果JS不写两套，桌面端的交互和移动端的交互很难针对平台作出差异</li>\n        </ul>\n    </ul>\n    <br>\n    <br>如果<b>你的</b><b>移动用户对网站所有的功能和内容有着与桌面用户同等的需求</b>，比如 新闻、报纸（媒体类）网站，或者活动、专题页等 <b>偏重信息传达而轻交互 </b>的网站，那么这个解决方案其实恰到好处：\n    <br><b>触摸屏优化（胖手指）、减少次要信息…… 这些通过 CSS 解决就够了。</b>\n    <br>\n    <br>\n    <br><b>但是，如果我想要做更多的 「移动化设计」，比如 减少信息层级、增强手势操作、让网页更接近一个Native App ？</b>\n    <br>\n    <br>好吧，为了更复杂的需求，为了我们的网站能更牛逼的 <b>「响应」</b> 各个平台，\n    <br>又有了这些解决方案：\n    <br>\n    <br>\n    <br>\n    <ul>\n        <li><b>服务器端（后端）：</b>\n            <br>\n        </li>\n        <ul>\n            <li>RESS （Responsive Web Design with Server Side Components）通过服务器端组件的响应式网页设计</li>\n        </ul>\n    </ul>提倡 RESS 的人认为：基于前端 CSS 的响应式方案只是一种妥协：\n    <br><b>“ UI 只是在很被动的进行「调整」，而不能真正达到各个平台的最优。好的设计应该达到「这个设备该有的体验」（Device Experiences）。 ”</b>\n    <br>\n    <blockquote><b>Device Experiences ：</b>A device experience is defined by how a device is most commonly used and the technical capabilities or limitations it possesses.</blockquote>RESS 的本质还是服务器端动态的生成，返回 HTML、JS、CSS、图像等资源文件，但是只使用同一个 URL 就可以提供给移动端定制化更强的网页，同时还大大节省了网络资源。\n    <br>\n    <br>\n    <br>\n    <ul>\n        <li><b>前端</b>（主要是JS），比如：\n            <br>\n        </li>\n        <ul>\n            <li>在 JavaScript 中实现两套逻辑，分别兼容键鼠、触摸设备</li>\n            <li>通过 UA、特性检测 在前端做设备判断，对资源进行异步加载，渲染不同模版</li>\n            <li>通过 特性检测 在前端做设备判断，使用不同的业务逻辑</li>\n            <li>前端的模块化也可以帮助解决这个问题，比如针对不同的平台加载不同的模块</li>\n            <li>……</li>\n        </ul>\n    </ul>\n    <br>\n    <br>这下，我们的网站可以更牛逼的 <b>“响应”</b> 各个平台了。\n    <br>（对，我还是称之为响应：这的确还是在<b>“响应”</b>啊 ，不是吗？）\n    <br>\n    <br>\n    <br><b>但是等下……</b>\n    <br>后端开发成本上去了，前端开发成本也上去了，配合着估计产品、设计资源也都上去了，<b>那我们为什么不干脆把 移动设备网站 和 桌面设备网站 分开呢！？</b>\n    <br>\n    <br>\n    <br>是啊，如果你的需求真的都到这一步了，你的移动网站也应该可以被称作 WebApp 了。<b>这种时候，把移动设备网站彻底分开或许真的是更好的选择。</b>\n    <br>\n    <br>开发资源如此充足，你还可以让专门的团队来维护移动端的网站。\n    <br>（嗯，BAT 就是这么干的）\n    <br>\n    <br>于是又一个概念来了：\n    <br>\n    <br>\n    <ul>\n        <li><b>独立的移动版网站</b> （按题主的话来说：手机和PC端分开来写）</li>\n    </ul>不过，它有那么独立么？\n    <br>我们知道，我们访问网站是通过 URL 来访问的。\n    <br>将移动网站 和 桌面网站 分开，如果不使用 RESS 技术，往往也就意味着要维护两个URL（不同的二级域名）\n    <br>难道我们要让所有桌面用户自觉访问 <a href=\"http://taobao.com\" class=\" external\" target=\"_blank\" rel=\"nofollow noreferrer\"><span class=\"invisible\">http://</span><span class=\"visible\">taobao.com</span><span class=\"invisible\"></span><i class=\"icon-external\"></i></a> ，所有 移动用户 都自觉访问 <a href=\"http://m.taobao.com\" class=\" external\" target=\"_blank\" rel=\"nofollow noreferrer\"><span class=\"invisible\">http://</span><span class=\"visible\">m.taobao.com</span><span class=\"invisible\"></span><i class=\"icon-external\"></i></a> ？\n    <br>\n    <br>不可能吧 ＝ ＝。\n    <br>\n    <br>于是，我们还是得依靠前端或服务器端的一次 <b>“响应”</b>（设备检测），做 URL 重定向，才能将不同设备的用户带到那个为他们准备的网站。\n    <br>\n    <br>\n    <br>\n    <br><b>所以其实在我看来，手机和PC端分开来写，只是 狭义响应式设计 的一种发展和延伸罢了。他们的界限没有，也并不需要那么清晰。</b>\n    <br>\n    <br>就如开题所引用的：\n    <br>\n    <blockquote><b>事实上，你可以认为如果一种设计不能兼顾两种平台的主要差别，就不能算是合格的响应式设计。</b>\n    </blockquote><b>“而无论是用什么解决方案。” —— 这句是我补的。</b>\n    <br>\n    <br>\n    <br>\n    <br>\n    <br>故我的结论是：\n    <br>\n    <br><b>这不是一个二选一的问题，而是选择一个合适的度</b>（你的桌面版本代码与移动版本代码分离、耦合的程度）\n    <br>\n    <br>而这个度，则是由你的设计需求决定的。\n    <br>而我们的需求原点其实也很简单：\n    <br>\n    <br> “<b>根据你的产品特点，进行两种不同的设计</b>”。\n    <br>\n    <br>\n    <br>以上。\n    <br>\n    <br>\n</div>\n"
  },
  {
    "path": "_posts/2014-12-13-wechat-block-kuaidi.markdown",
    "content": "---\nlayout:     post\ntitle:      \"如何看待微信屏蔽快的打车事件？\"\nsubtitle:   \"恰有小感。\"\ndate:       2014-12-13\nauthor:     \"Hux\"\nheader-img: \"img/post-bg-kuaidi.jpg\"\ntags:\n    - 知乎\n    - 产品\n---\n\n> 这篇文章转载自[我在知乎上的回答](http://www.zhihu.com/question/26774049/answer/35041458)\n\n\n<div>\n    唉。今天恰巧有感，过来小聊几句。\n    <br>还是要先声明下：<b>所有言论出自个人，与阿里和我所在的团队无关。</b>\n    <br>\n    <br>\n    <br>正文。\n    <br>\n    <br>应该很多互联网公司都有这项 “福利” ——<b> 加班到X点以后，报销打车费</b>。\n    <br>阿里大约是晚上9点。\n    <br>\n    <br>初进阿里时还不习惯，想着6点下班后，吃个免费晚饭，赶快坐地铁回家。\n    <br>后来一是发现高峰期的地铁简直要命，二是确实有太多需求做不完， 平常经常会说： “这个我们晚上再谈…”\n    <br>\n    <br>所以晚上加班就成了公司里很多人的常态 ，就算今天 8 点多就已经工作得差不多了，也会习惯性得等到 9 点左右，<b>叫个车回家</b>。\n    <br>\n    <br>于是，每天 9 ～ 12 点间，公司里的叫车声、电话约车声、络绎不绝。我们团队私下里也有个微信群，用以和工作的旺旺群区分。<b>在打车软件玩起红包返现后，大家就都会在群里分享叫车红包，52个人的群，有时一分钟内不抢，红包就没了。</b>\n    <br>\n    <br>\n    <br>众所周知的，阿里和快的打车的关系。\n    <br>\n    <br>所以群里好像约定俗成般的，从来就没有出现过滴滴的红包。<b>而由于红包返现利滚利带来的超强用户粘性，大家连叫车也都开始只用快的了。</b>\n    <br>\n    <img class=\"shadow\" src=\"/img/in-post/post-kuaidi-1.jpg\" width=\"260\">\n    结果好景不长，微信突然就玩了这么一手，直接把快的打车屏蔽了。\n    <br>当天大家就发现了，还讨论了下对策……<b>比如什么「先分享到微博，然后把链接复制出来，再发到旺旺群」……</b>\n    <br>\n    <br>嗯。我 TM 也觉得挺拼的。。\n    <br>于是大约微信群就沉寂了一天…\n    <br>\n    <br><b>然后才第二天……第一个滴滴红包就在群里出现了！</b>那时的文案还是什么：“<i>4个小伙伴，3个用滴滴！红包召唤新伙伴归队啦！</i>”\n    <br><b>我我我我当时就不由自主的纠结了一会儿 “价值观” ，放下手机 debug 去了……</b>\n    <br><b>等我再想起来，点开链接一看：特么的……「红包已抢完」。</b>\n    <br>\n    <br>。。。\n    <br><b>再后来。</b><b>就根本收不住了，滴滴红包那个飘。</b>\n    <br>\n    <br>\n    <br>唉其实我就是想说：<b>这也就一天……用户习惯就被彻底干翻过来了。 就算是盟友…也没救。</b>\n    <br>\n    <img class=\"shadow\" src=\"/img/in-post/post-kuaidi-2.jpg\" width=\"260\">\n    所以我今天还是打着滴滴回来的……分享红包的一瞬间，心里突然一阵小惆怅。就回来写下了这段答案。\n    <br>\n    <br>说了半天，好像也没说到什么干货…权当故事听吧。\n    <br>其实你要问我这有没有违反互联网平等开放法则什么的。我觉得上面 <a data-hash=\"8f7d284bb1a97deaa4533a6190206ecb\" href=\"http://www.zhihu.com/people/8f7d284bb1a97deaa4533a6190206ecb\" class=\"member_mention\" data-editable=\"true\" data-title=\"@覃浩tommy\" data-tip=\"p$b$8f7d284bb1a97deaa4533a6190206ecb\">@覃浩tommy</a><a data-hash=\"43d639a3d763d3dad948e0bc4c645eec\" href=\"http://www.zhihu.com/people/43d639a3d763d3dad948e0bc4c645eec\" class=\"member_mention\" data-editable=\"true\" data-title=\"@赛门\" data-tip=\"p$b$43d639a3d763d3dad948e0bc4c645eec\">@赛门</a> 都说得挺好的，两种思路而已，大家可以自行选择。\n    <br>\n    <br>但是关于怎么看待，其实这次我以普通用户的身份来说……真心觉得：<b>「小良心小正义感在强需求面前真特么太弱了」</b><b>。</b><b>更何况这个强需求被干掉的同时还双手奉上了替代品。</b>\n    <br>\n    <br><b><u>所以大厂们你们就使劲撕逼吧，需要打到用户脸时，多给糖多给枣就好了。</u></b>\n    <br>\n    <br>\n    <br>哦对了，今天微信宣布朋友圈内限制分享未备案网页了。\n    <br><b>枣呢 ！？！？</b>\n    <br>\n    <br>\n</div>\n"
  },
  {
    "path": "_posts/2015-03-10-apple-event-2015.markdown",
    "content": "---\nlayout:     post\ntitle:      \"如何评价 2015 年 3 月 9 日 Apple 春季发布会？\"\nsubtitle:   \"聊聊科技与新式奢侈品\"\ndate:       2015-03-10 12:00:00\nauthor:     \"Hux\"\nhidden: true\nheader-img: \"img/post-bg-apple-event-2015.jpg\"\ntags:\n    - 知乎\n    - 产品\n---\n\n> 这篇文章转载自[我在知乎上的回答](http://www.zhihu.com/question/28617408/answer/41626694)\n\n\n<div>\n    <blockquote>一个 gay，一个 gay-like ，带着 Apple 向着<b>新式奢侈品</b>的方向飞去了。</blockquote>\n    <br>无论是 Apple Watch ，还是 new MacBook，这次发布会都象征着 Apple 更明显的转型。\n    <br>\n    <br>不应该再把 Apple 跟 Microsoft 简单粗暴的对比，它们的受众产生了愈大的差异。两家公司对数字时代有着完全不同的战略，它们改变世界的思路，跟盖茨-乔布斯时代比有着更巨大的分歧。\n    <br>\n    <br>MS 还是 MS，就像纳德拉 7 月的全员信，微软的战略还是回到了<b>“生产力”。</b>其实微软对“极致”，对“未来”的追求是一种很直观的，我们最初理解的科技，比如手势交互、虚拟现实、机器化自动化、高效办公什么的。微软的受众更多的也还是面向生产力、工作群体（工程师、办公人员）。所以软狗们在知乎永远可以说微软 blah blah，因为对于这部分场景，微软确实有着不可替代的牛逼。\n    <br>\n    <br><b>而 Apple 则逐渐转变成为数字时代的 LV。</b>这并不是说它放弃了科技，而是“科技追求极致”的另一种可能性 —— 科技与人文的交汇，甚至是科技与时尚的跨界融合。\n    <br>\n    <br>让我们来稍稍想象一下未来：\n    <br>\n    <br>科技与生活的融合一定是越来越紧密的。更多的“物件”将与科技结合，而这些智能设备也将越来越普及，它们面向的人群，会越来越宽，直到覆盖所有人。\n    <br>可以说现在的科技还是很生硬的，我们很容易把科技和 Geek、Nerd 联系在一起。当一个东西和科技沾边时，我们往往会很清楚的意识到：“哦，这是一个科技产品”，于是我们忽略了其他东西，更多的去关注它的科技性（功能性），但是未来不一样。\n    <br>未来的科技将会很平常，未来的科技将会更加隐形，就像现在的眼镜、家具、衣服、箱包……普通人谁还会在乎它们背后复杂的材料科学与工艺？我们只会觉得它们是生活必需品，然后去在乎它们的外观、舒适性，挑选自己喜欢的产品。\n    <br>\n    <br><b>科技也一样，当科技无处不在时，我们对“科技产品”本身的功能性要求，就不再是唯一的考量。</b>\n    <br>\n    <br>\n    <br>LV 的包之所以成为奢侈品，不止是因为“当它作为一个包时，它的功能性（选材、做工）非常优秀，结实耐用”，还因为它的艺术性，观赏性，精致感，幸福感，社会价值等等，带来的种种溢价。\n    <br>\n    <br>而 Apple Watch、new MacBook，很明显在做相同的事情。\n    <br>\n    <br>说到奢侈，“奢侈”这两个字，在我国基本上是贬义的，词典里的翻译是<b>“挥霍浪费钱财，过分追求享受”</b>，但 Luxury 在英文中其实要中性许多。\n    <br>\n    <br>与旧式奢侈相比，新奢侈主义在这一代中产消费者中则被广泛接受。所谓新奢侈主义指的是在同类产品中服务质量更高，品位更高的产品，让消费者心驰神往。它们价格不菲，但是还不至于昂贵到可望不可即。\n    <br>\n    <blockquote>德国的实业家拉茨勒在《奢侈带来富足》(2001)一书中对旧式奢侈和新式奢侈做过有趣的论述。他以手机为例说明了两种方式的不同：如果一部手机是因为其先进的技术和为客户提供超值的功能而使价格出众，那么生产和消费这样的手机就是需要倡导的新式奢侈；相反，如果一部手机不是因为卓越的技术性能，而是因为手机套上了嵌有钻石的黄金外壳而使得价格昂贵，那么生产和消费这样的手机就是令人憎恶的旧式奢侈。\n        <br>\n    </blockquote>补充一下：<b>这句话出自 2001 年，放在现在来看其实并不是完全适用的。</b>\n    <br>\n    <br>手机对当今社会的意义早已不是简单的通讯设备。真正的区别还是在那句话：“Design is about how it works”，<b>新式奢侈的内涵在于产品的某个设计是真的有意义，还是单纯的为了贵而贵。</b>\n    <br>对于当今数码产品，工业设计、艺术设计是其作为消费品非常重要的部分，如果你是为了给用户提供更多的外观选择而使用黄金，或是为了硬度使用钻石。而不是单纯的堆砌它们来增加价格，那么这些设计都是符合“新式奢侈”的内涵的。\n    <br>\n    <br>所以当我们回过头看看 new MacBook，私以为是<b>数字产品界新式奢侈品</b>的典型。\n    <br>\n    <br>当我们吐槽 Apple 为了极致的轻薄牺牲了主频、风扇、接口，当我们吐槽买它就是买电池，当我们拿它与 MBA、MBP、Surface 对比吐槽它的 “参数/价钱比” ……\n    <br>\n    <br>其实人家的受众是那些有消费能力追求生活质量的 Sir or Lady，它们并不需要天天对着电脑做开发、重型办公或者打游戏，对于只需要便携安静（轻薄＋续航＋无风扇）、看看电影（Retina Display）、又希望无时不刻彰显自己的品味与身份（外观优雅＋极致设计）的他们来说，new Macbook 简直是最适合“佩戴”的轻奢品。\n    <br>\n    <br>\n    <br>有人说 Apple Watch 简直是 Jony Ive 这个一心向往做奢侈品设计的天才将 Apple 引入了歧途里，而我却觉得<b>科技与时尚的结合为何就不是一件美丽的事情？</b>\n</div>\n"
  },
  {
    "path": "_posts/2015-03-25-digital-native.markdown",
    "content": "---\nlayout:     post\ntitle:      \"hUX 随想录（一）：Digital native 数字原住民\"\nsubtitle:   \" 两岁的侄女天天叫着手机手机 \"\ndate:       2015-03-25 \nauthor:     \"Hux\"\nheader-img: \"img/post-bg-digital-native.jpg\"\ncatalog: true\ntags:\n    - hUX 随想录\n    - UX/UI\n---\n\n> 那是一种与生俱来的天赋，就好像矮人天生擅长舞锤，而精灵则拥有魔法庇护。那些数字时代的原住民们，天生具备着一种操纵数字世界的领悟。\n\n## 前言\n\n从 2010 年 iPhone 4 横空出世席卷中国，到时隔不到半月的 Apple 2015 发布会。短短几年里，身边就几乎再也看不到“非智能手机”的身影了。\n\n想想发布那时（2010.6.8），博主应该还是一个高一小屁孩，等着暑假快点到来。虽然父上大人用着 iPhone 3GS ，不过那时我对 Apple 可没啥感觉，还用着后来被 Apple 干翻的 Nokia （5320），抱着算是被 Apple 干翻的 IBM ，偶尔玩玩后来被 Apple 干翻的 Adobe Flash……  \n虽然不是含着着金 iPhone 出生的一代，但好歹也算是摸着电脑长大的一代人，估摸着也算是 **Digital native** 了。你说这词是什么意思？别急，我们慢慢说。\n\n\n## 正文\n\n今年暑假回了两个老家，也看望了不少长辈。  \n长辈们的手机果然都进行了可以想见的升级，除了爷爷奶奶辈外，清一色的 iPhone 或者 Android 4.2+ ，呃，没有 WP。\n\n智能手机啊智能手机，Smart Phone —— 聪明又能干的手机。可是每每我看到年龄稍微大点的长辈们顶着一附花镜，瞪大了眼睛，一只手托着，另一只手则伸出一根手指小心翼翼得戳着硕大的屏幕时，我就瞬间觉得这哪里是 Smart ，分明是 Stupid Phone 。于是我就看着父辈们不厌其烦得教着老人家如何解锁，如何打电话，回短信。却又常常要像子女们请教微信里的图片存到了哪（这基本都是 Android 的毛病），朋友圈的文章如何分享转发，视频和小视频为什么不一样，视频通话怎么玩这一类“高级问题”。  \n\n这现象既尴尬又有趣，至少我 10+ 岁时还觉得自己什么都得请教父母。可是这一代孩子，居然能天天被父母请教手机问题然后理直气壮得回一句：“你怎么连这都不会？”  \n\n<br>\n**最让我惊讶的还是我两岁的小侄女阿布。**  \n两岁的小孩子，刚刚能跑能跳，学会说话也不久，甚是可爱。\n\n第一次感受阿布的神奇，是跟阿布和阿布爸（姐夫）在车的后座上坐着，阿布突然就向姐夫喊起了“手机，要手机…”。“就玩一会儿哦” 于是姐夫从口袋里掏出了 iPhone ，放到了比手机小好几号的小手上。我第一反应只是觉得好玩，大概小孩子觉得这个黑漆漆但是又能被点亮的“玩具”很好玩吧，姐夫和姐姐又无时不在教小孩子认东西，小孩子记得这个“玩具”叫作手机也很正常。\n\n**紧接着阿布就用她的行为狠狠得打了我的脸：Home 键 → 滑动解锁 → 照片 App → 点开一张照片然后开始左翻右看；一串 Combo 动作娴熟一气呵成。**  \n\n大家脑补一下柯南那个“脑海中‘唰’的一道亮光”！对对我当时就是这样，**然后就犯了职业病，连续几天都开始观察阿布是如何玩手机的。**（小孩子玩手机不好，是要控制时间的）\n\n\n#### I. 超强的学习能力\n\n小孩子的大脑思维简单却又有着惊人的学习能力，他们十分擅长模仿，而且能非常高效的对信息进行记忆和处理。\n\n**我确信阿布已经在无数次学习中完美得理解了 Home 键的含义。**阿布知道主屏上的每一个长得一样的东西（App Icon）都可以点击，点击之后就会进入一个新的东西，如果阿布不喜欢，她知道按 Home 键返回主屏。\n\n阿布不完全具备分辨众多 icon 的能力，但唯独最喜欢“照片”这个应用，她总是可以在几次划屏之后找到并打开它。  \n**可以说理解下图 “主屏幕与应用” 这样的一级逻辑是相对比较轻松的，而且 Home 键作为物理按键，认知成本也比屏幕中的虚拟按钮要低得多。**\n\n<pre>\n    Icon\n主屏幕 ⇌ 应用\n    Home \n</pre>\n\n可是接下来阿布在照片应用内的表现就足以说明问题：阿布不但能够对“照片方块”进行归类学习，知道**“既然一张照片可以点开，那么每张都是可以的”**。阿布居然还学会了 **Back 按键**的使用！  \n要知道阿布是一定不认识 Back 箭头右边的文字的。我猜测阿布可能是通过空间位置记忆（屏幕左上角），也有可能是通过图形记忆的（要知道人对图形的认知能力要远高于文字）。总之无论如何，阿布学会了 Back ，并可以进行下图这样“如此复杂的操作”了：\n\n<pre>\n  照片Icon     One   One\n主屏幕 ⇌ 相簿列表 ⇌ 相簿 ⇌ 单张照片\n    Home      Back  Back\n</pre>\n\n而且其实在“单张照片”这个环节是有个“坑”的：**如果点一下照片，所有导航会消失（切换到照片全屏观看模式），要再点一下照片导航才会回来。** 我不能清楚的知道阿布是否了解了这个规律，但是一旦阿布看到 Back 键回来时就会懂得依靠按它来返回。\n\n\n#### II. 完美理解隐喻\n\n小孩子的思维是直白的。它们不会试图掩盖什么想法，它们想到什么就会去做什么。\n\n我们都知道如果一个东西在你的右边，那么你需要把这个“世界”向左拉，做一个相对运动，你才能重新看到这个东西。小孩子不用知道什么相对运动，但是自然而然的就能懂 —— **阿布知道在屏幕上左右划能让手机里的这个小世界跟着移动起来，阿布知道被划走的东西相反划就可以划回来。**\n\n这就是我们常说的**物理隐喻**，小孩子不知道物理也不知道什么隐喻，But it works.\n\n不过让我惊讶的不是这个，我 2 岁的时候要是有 iPhone ，我应该也是能那么瞎扒拉一两下的吧……  \n真正让我觉得非写此文不可的是：有一次，我给阿布玩我的 iPhone ，阿布照常打开了相册开始翻，**说时迟那时快，来了一条微信通知！**\n\n对对对，就是那个从上往下滑下来 ↓↓↓↓ 的 Push Notification.\n\n<pre>\n微信 \nKant 给你发了一个红包 \n</pre>\n\n**接着高潮就来了，阿布非常淡定的伸出小手，把推送给我顶 ↑↑↑↑ 回去了！！**  \n卧槽你们一定不能体会我当时有多惊讶。\n\n**隐喻啊！从上面掉下来的东西，不 想 要 的 话 就可以划回去好吗。** 小孩子对数字世界交互隐喻的理解，真是完爆了不知道多少 Digital immigrant (下文会解释) 。\n\n\n####  III. 世界观的树立\n\n这是为什么？为什么小孩子可以具备对数字世界如此的领悟能力？\n\n我的答案不难理解：**数字世界已经完美地融入了阿布的世界体系里。阿布从小就在感受数字世界的“定律”，这种学习，对于阿布来说，与她对现实世界的学习完全无异。**\n\n**这种感觉就好像我们从小其实就在感受这个世界的物理规律**：我们不知道万有引力，但是我们知道东西从手中放开就会掉下去；我们不知道热交换，但是我们知道冷水和热水可以对成温水；我们不知道杠杆原理，但是我们知道在门把手附近推门会更省力……\n\n有个很好玩的案例可以证明阿布脑中体系的建立过程：我的相册中有不少 UI 截屏，**截屏对于阿布来说是个更有难度的认知（就好像大多数动物都无法认知镜子一样）** 。当 Back 按钮成为阿布脑海中对虚拟世界“返回”的定义，就算是截屏中的 Back ，阿布也会毫不犹豫的点上去，可是居然没有效果 —— **这违背了阿布的认知，于是她会感到疑惑和不安**，直到下一次 Back 奏效……\n\n世界观是一个需要长时间建立起来的东西，**当我们跟小孩子一样对世界最为无知时，我们也对世界最为好奇，于是眼前的一切都一股进脑。然后大脑进行着快速的记忆和学习，逐渐形成了你对这个世界的认知。**\n所以世界观也是一个很顽固的东西，已经建立起来的部分很难摧毁，新的东西也就没有太多立足之处 —— 这也算是解释了为什么小孩子学习数字设备如此之快，而越是大龄就相对越难接受（当然这其实与不同年龄大脑的生命活动有关系，这里只是比喻的说法）\n\n说到这里，我们终于可以回归最初的问题：  \n什么是 Digital native ？还有与之对应的 Digital immigrant ?\n\n> **Digital native，数字原住民**： 指代从出生开始就习惯有互联网、无线技术的一代人 （logically there's a whole generation of individuals for whom concepts such as the Internet and wireless technology are just humdrum, because they've never lived in a world where they didn't exist. These are the so-called digital natives）\n>    \n>  **Digital immigrant，数字移民**：指代更早的一代人，已经情愿或者不情愿地适应了这个数字世界，并且将各类数字工具运用到生活当中。（Digital immigrants are their antithesis, being the folks born earlier who, either reluctantly or enthusiastically, have adapted to the digital world and incorporated its tools into their lives.）\n\n定义如此，但其实边界模糊。而真正重要的是：**或许在这个飞速发展的世界里，只有保持小孩般的好奇与初心，才能不被时代轻易的抛弃。**\n\n## 结语\n\n我一度欣喜阿布是不是将来要成为计算机或者交互领域的大师，可是转念一想**我更愿意相信这一代小孩子都将具备如此神力**。就好像世界如果重新建立了秩序，那么最先适应秩序的一定是在新秩序下诞生的孩子们。因为他们对世界的认知里没有任何过去，也就没有任何 boundary 。\n\n我经常想象假如我出世在一个以魔法为秩序的纪元里，那个世界里的小孩子一定生来就具备对魔法的领悟与操纵能力。**我想那种能力或许不是血脉或者种族里自带的天赋吧，而是从你呱呱坠地，开始认知、学习这个世界的那一天起，魔法就习以为常地印在了你的世界观里。**你从小就知道母亲空手就可以变个小太阳温暖你，而父亲则可以挥挥手放出一片星空来逗你开心。\n\n**于是你坚定不移，当你第一次有力气挥动你的小胳膊时，一道流星划过天际。**\n\n\n"
  },
  {
    "path": "_posts/2015-03-31-e2e_user_scenarios.markdown",
    "content": "---\nlayout:     post\ntitle:      \"Definition of End to End User Scenarios\"\ndate:       2015-03-31\nauthor:     \"Hux\"\nheader-img: \"img/post-bg-e2e-ux.jpg\"\npublished: false\nlang: en\ntags:\n  - UX/UI\n  - En\n---\n\n\n### End to end?\n\nTo explain what is \"End to End User Scenarios\", we should first explain what is \"End to End\", which we can called E2E for short.\n\nThere is not a very clear definition of E2E in wiki.<sup>[[1]](#ref1)</sup> In dictionary, it can both refer to \"throughout\" or \"the end of one object connect to the end of another object\".<sup>[[2]](#ref2)</sup>\n\nE2E is usually used in Logistics, Computer Networking and Software Testing. For example, End-to-end testing is a methodology used to test whether the flow of an application is performing as designed from start to finish. The entire application is tested in a real-world scenario.\n\nSo in my view, the most essential part of E2E is that **we must focus on the entire process, including every parts in a use case.**\n\n\n### User Scenarios!\n\nUser scenarios is a common term in UX Design,<sup>[[3]](#ref3)</sup><sup>[[4]](#ref4)</sup> which expands upon our persona and user stories by including details. It told us about users' motivation, goals and actions on our products.\n\nTo make it better, there comes **\"End to End User Scenarios\", not just tell a fragment of users' activities, but pay attention to the entire process the user undergoes.**\n\nThat means we should consider the whole things from the start point that user want to use our products to the ended up point that user get results and leave our products.\n\nOnly when we know **who** does **what** on our products, **how** and **why** they do it, can we define design requirements concrete enough to actually meet them. So it really helps us to improve our UX of our products.\n\n\n### Let's go deeper...\n\nWe just put the two terms together and give it a explanation, but it can be farther. When we truly design an experience, End to End User Scenarios can helps more:\n\n* **Extend the scope**\n\nThere is a interesting instance <sup>[[5]](#ref5)</sup> told that sometimes we are already satisfy of our designed UX, but if we look beyond the both ends of the designed experience by extending the scope of the timeline before and after… we may sadly realize that it’s a complete car crash outside the scope of the designed experience...\n\nTry to extend the scope and consider more, so can we design a much broader experience for our user.\n\n* **Shorten the path**\n\nUX Designers always dive into a User Flow and try to shorten the user paths. The idea of End to End User Scenarios can do the same things.\n\nFor example, in the past, if I want to know the weather today. I should typically visit a search engine website, input and search \"weather\", click the first link that search result page shows, then jump into a kind of weather website like \"The Weather Channel\", and finally, I got today's weather information!\n\nBut wait! **Just consider it using \"End to End User Scenarios\"**, I just want to know about weather so I use search engine right? why should I took a so long user path to get there? Smart Search Engine should told me the weather directly.\n\nThat is what all search engine have doing nowadays.\n\n\n### In sum\n\nThere is many design tools like \"End to End User Scenarios\" were used by designers, they are really awesome. But the most essential things in my opinion is, still, always thinking about user. All this tools are powerful only based on a truly user-centric mind.\n\nFrom my perspective, the \"End to End User Scenarios\" can be generally defined as **\"Entire Process Considered, User Requirement Centric, Anticipated Experince Design\".**\n\n\n\nThat's all, thank you.\n\n### References\n\n1.<a id=\"ref1\">[End-to-end - Wikipedia, the free encyclopedia](http://en.wikipedia.org/wiki/End-to-end)</a>\n\n2.<a id=\"ref2\">[end-to-end - definition of end-to-end by The Free Dictionary](http://www.thefreedictionary.com/end-to-end)</a>\n\n3.<a id=\"ref3\">[How User Scenarios Help To Improve Your UX - The Usabilla Blog](http://blog.usabilla.com/how-user-scenarios-help-to-improve-your-ux/)</a>\n\n4.<a id=\"ref4\">[How to Create User Stories, Scenarios, and Cases](https://www.newfangled.com/how-to-tell-the-users-story/)</a>\n\n5.<a id=\"ref5\">[Designing end-to-end user experiences. | 90 Percent Of Everything](http://www.90percentofeverything.com/2008/11/11/designing-end-to-end-user-experiences/)</a>\n"
  },
  {
    "path": "_posts/2015-04-14-unix-linux-note.markdown",
    "content": "---\nlayout:     post\ntitle:      \"Unix/Linux 扫盲笔记\"\nsubtitle:   \"不适合人类阅读，非常水的自我笔记\"\ndate:       2015-04-14 \nauthor:     \"Hux\"\nheader-img: \"img/post-bg-unix-linux.jpg\"\ncatalog: true\ntags:\n  - 笔记\n---\n\n> This document is not completed and will be updated anytime.\n\n\n## Unix \n\n\n> Unix is a **family** of multitasking, multiuser computer OS.\n\n\nDerive from the original **AT&T Unix**, Developed in the 1970s at **Bell Labs** (贝尔实验室), initially intended for use inside the **Bell System**.\n\n- #### Bell Labs\nBell 和 AT&A 在那时已经是一家了，可以看到那时的通信公司真是一线 IT 公司呢。 \n**C 语言也是 Bell Labs 的产物**，从一开始就是为了用于 Unix 而设计出来的。所以 Unix （在 73 年用 C 重写）在高校流行后，C 语言也获得了广泛支持。\n\n\n\nAT&T licensed Unix to outside parties(第三方) from the late 1970s, leading to a variety of both **academic** (最有有名的 BSD ) and **commercial** (Microsoft Xenix, IBM AIX, SunOS Solaris)\n\n- #### Xenix\n微软 1979 年从 AT&A 授权来的 Unix OS，配合着 x86 成为当时最受欢迎的 Unix 发行版。后来 M$ 和 IBM 合作开发 OS/2 操作系统后放弃，后来最终转向 **Windows NT**。\n\n- #### BSD\n**Barkeley Software Distribution**, also called Berkeley Unix. Today the term \"BSD\" is used to refer to any of the BSD descendants(后代) which together form a branch of the family of Unix-like OS.(共同组成了一个分支)\n\t- **BSD 最大的贡献是在 BSD 中率先增加了虚拟存储器和 Internet 协议**，其 TCP/IP(IPv4 only) 代码仍然在现代 OS 上使用（ Microsoft Windows and most of the foundation of Apple's OS X and iOS ）\n\t- BSD 后来发展出了众多开源后代，包括 FreeBSD, OpenBSD, NetBSD 等等……很多闭源的 vendor Unix 也都从 BSD 衍生而来。\n\n- #### FreeBSD & Apple\nFreeBSD 不但是 Open Source BSD 中占有率最高的，还直接影响了 Apple Inc : NeXT Computer 的团队在 FreeBSD 上衍生出了 NeXTSTEP 操作系统，这货后来在 Apple 时期演化成了 **Darwin** ，这个“达尔文”居然还是个开源系统，而且是 the Core of **Mac OS X** and **iOS**.\n\n- #### NeXTSTEP\nAn **object-oriented**, multitasking OS. Low-level C but High-level OC language and runtime the first time, combined with an **OO aplication layer** and including several \"kits\".    \n大家都知道 NeXT 是 Steve Jobs 被 forced out of Apple 后和 a few of his coworkers 创办的，所以 **NeXTSTEP 绝对是证明 Jobs 实力的作品。** \n\n- #### Darwin\n[Darwin](https://en.wikipedia.org/wiki/Darwin_(operating_system)), the core set of components upon which Mac OS X and iOS based, mostly POSIX compatible, but has never, by itself, been certified as being compatible with any version of **POSIX**. (OS X, since Leopard, has been certified as compatible with the Single UNIX Specification version 3)  \n**所以说 Mac OS X 算是很正统 Unix 的了**\n\n- #### POSIX\n可移植操作系统接口, Portable Operating System Interface, is a family of standards specified by the IEEE from maintaining compatibility between OS, defines the API along with Command Line Shells and utility interfaces, for software comaptibility with variants of Unix and other OS.\n\t- Fully POSIX compliant:\n\t\t- OS X\n\t\t- QNX OS (BlackBerry)\n\t- Mostly complicant:\n\t\t- Linux\n\t\t- OpenBSD/FreeBSD\n\t\t- Darwin (Core of **iOS** & OS X)\n\t\t- **Android**\n\t- Complicant via compatibility feature （通过兼容功能实现兼容）\n\t\t- Windows NT Kernel\n\t\t\t- Windows Server 2000, 2003, 2008, 2008 R2, 2012\n\t\t- Symbian OS (with PIPS)\n\t\t\t- Symbian was a closed-source OS.\n\n\n## Unix-like\n\n> A Unix-like (sometimes referred to as UN*X or *nix) operating system is one that behaves in a manner similar to a Unix system, while not necessarily conforming to or being certified to any version of the **Single UNIX Specification**.\n\nThere is no standard for defining the term.  \n其实 Unix-like 是个相对模糊的概念：\n\n* 最狭义的 Unix 单指 Bell Labs's Unix \n* 稍广义的 Unix 指代所有 Licensed Unix, 即通过了 SUS 的 Unix-like ，比如 OS X\n* 最广义的 Unix 即所有 Unix-like 系统，无论它是否通过过任何 SUS，包括 Linux，BSD Family 等\n\n#### Single UNIX Specification\nThe Single UNIX Specification (SUS) is the collective name of a family of standards for computer OS, compliance with which is required to **qualify for the name \"Unix\"**, like **POSIX**.\n\n#### Apple iOS\niOS is a **Unix-like OS based on Darwin(BSD)** and OS X, which share some frameworks including Core Foundation, Founadtion and the Darwin foundation with OS X, but, Unix-like shell access is not avaliable for users and restricted for apps, **making iOS not fully Unix-compatible either.**\n\nThe iOS kernal is **XNU**, the kernal of Darwin.\n\n#### XNU Kernel\nXNU, the acronym(首字母缩写) for ***X is Not Unix***, which is the **Computer OS Kernel** developed at Apple Inc since Dec 1996 for use in the Mac OS X and released as free open source software as part of Darwin.\n\n\n## Linux\n\n\n> Linux is a Unix-like and mostly POSIX-compliant computer OS.\n\n\n![Unix_timeline](http://upload.wikimedia.org/wikipedia/commons/thumb/c/cd/Unix_timeline.en.svg/800px-Unix_timeline.en.svg.png)\n\n\n#### Linux Kernel\n\n严格来讲，术语 Linux 只表示 [Linux Kernel](http://en.wikipedia.org/wiki/Linux_kernel) 操作系统内核本身，比如说 Android is Based on Linux (Kernel). Linus 编写的也只是这一部分，一个免费的 Unix-like Kernel，并不属于 GNU Project 的一部分。\n\n但通常把 Linux 作为 Linux Kernel 与大量配合使用的 GNU Project Software Kit (包括 Bash, Lib, Compiler, 以及后期的 GUI etc) 所组合成的 OS 的统称。（包括各类 Distribution 发行版）\n\n这类操作系统也被称为 **GNU/Linux**\n\n\n#### GNU Project\n\nThe GNU Project is a **free software, mass collaboration** project, which based on the following freedom rights:\n\n* Users are free to run the software, share (copy, distribute), study and modify it.\n* GNU software guarantees these freedom-rights legally (via its license).\n* So it is not only FREE but, more important, FREEDOM.\n\nIn order to ensure that the *entire* software of a computer grants its users all freedom rights (use, share, study, modify), even the most fundamental and important part, **the operating system**, needed to be written. \n\nThis OS is decided to called **GNU (a recursive acronym meaning \"GNU is not Unix\")**. By 1992, the GNU Project had completed all of the major OS components except for their kernel, *GNU Hurd*. \n\nWith the release of the third-party **Linux Kernel**, started independently by *Linus Torvalds* in 1991 and released under the GPLv0.12 in 1992, for the first time it was possible to run an OS **composed completely of free software**.\n\nThough the Linux kernel is not part of the GNU project, it was developed using GCC and other GNU programming tools and was released as free software under the GPL.\n\nAnyway, there eventually comes to the **GNU/Linux**\n\n\n* **GPL**: GNU General Public License\n* **GCC**: GNU Compiler Collection\n\n其他与 GPL 相关的自由/开源软件公共许可证：\n\n* [Mozilla Public License](http://en.wikipedia.org/wiki/Mozilla_Public_License)\n* [MIT License](http://en.wikipedia.org/wiki/MIT_License)\n* [BSD Public License](http://en.wikipedia.org/wiki/BSD_licenses)\n\t* GPL 强制后续版本必须是自由软件，而 BSD 的后续可以选择继续开源或者封闭\t\n* [Apache License](http://en.wikipedia.org/wiki/Apache_License)\n\n\n![Public License](/img/in-post/open-source-license.png)\n\n#### Android\n\nAndroid is a mobile OS based on **Linux Kernel**, so it's definitely **Unix-like**.  \n\n**Linux is under GPL so Android has to be open source**. \nAndroid's source code is released by Google under open source licenses, although most Android devices ultimately ship with a combination of open source and proprietary software, including proprietary software developed and licensed by Google *(GMS are all proprietary)*  \n\n#### Android Kernel\n  \nAndroid's kernel is based on one of the Linux kernel's long-term support (LTS) branches.   \n\n**Android's variant of the Linux kernel** has further architectural changes that are implemented by Google outside the typical Linux kernel development cycle, and, certain features that Google contributed back to the Linux kernel. Google maintains a public code repo that contains their experimental work to re-base Android off the latest stable Linux versions.\n\nAndroid Kernel 大概是 Linux Kernel 最得意的分支了，Android 也是 Linux 最流行的发行版。不过，也有一些 Google 工程师认为 Android is not Linux in the traditional Unix-like Linux distribution sense. 总之这类东西就算有各种协议也还是很难说清楚，在我理解里 Android Kernel 大概就是 fork Linux Kernel 之后改动和定制比较深的例子。\n\n\n#### Android ROM\n\n既然提到 Android 就不得不提提 Android ROM \n\nROM 的本义实际上是只读内存：  \n\n**Read-only memory** (ROM) is a class of storage medium used in computers and other electronic devices. Data stored in ROM can only be modified slowly, with difficulty, or not at all, so it is **mainly used to distribute firmware (固件)** (software that is very closely tied to specific hardware, and unlikely to need frequent updates).\n\nROM 在发展的过程中不断进化，从只读演变成了可编程可擦除，并最终演化成了 Flash  \n\n* PROM (Programmable read-only memory)\n* EPROM (Erasable programmable read-only memory)\n* EEPROM (Electrically erasable programmable read-only memory)\n\t* Flash memory (闪存) \n\nFlash 的出现是历史性的，它不但可以作为 ROM 使用，又因其极高的读写速度和稳定性，先后发展成为U盘（USB flash drives）、移动设备主要内置存储，和虐机械硬盘几条街的固态硬盘（SSD），可以说这货基本统一了高端存储市场的技术规格。\n\n所以我们平时习惯说的 ROM 其实还是来源于老单片机时代，那时的 ROM 真的是写了就很难（需要上电复位）、甚至无法修改，所以那时往 ROM 里烧下去的程序就被称作 firmware ，固件。久而久之，虽然技术发展了，固件仍然指代那些不常需要更新的软件，而 ROM 这个词也就这么沿用下来了。\n\n所以在 wiki 里是没有 Android ROM 这个词条的，只有 [List of custom Android firmwares](http://en.wikipedia.org/wiki/List_of_custom_Android_firmwares)\n\n> A custom firmware, also known as a custom ROM, ROM, or custom OS, is an aftermarket distribution of the Android operating system. They are based on the Android Open Source Project (AOSP), hence most are open-sourced releases, unlike proprietary modifications by device manufacturers.\n\n各类 Android ROM 在 Android 词类下也都是属于 **Forks and distributions** 一类的。\n\n所以我说，其实各类 Android ROM 也好，fork Android 之流的 YunOS、FireOS 也好，改了多少东西，碰到多深的 codebase ……**其实 ROM 和 Distribution OS 的界限是很模糊的**，为什么 Android 就不可以是移动时代的 Linux ，为什么 Devlik/ART 就不能是移动时代的 GCC 呢？\n\n#### Chrome OS\n\nChrome OS is an operating system based on the **Linux kernel** and designed by Google to work with web applications and installed applications. \n\n虽然目前只是个 Web Thin Client OS ，但是 RoadMap 非常酷……\n\n* **Chrome Packaged Application** (Support working offline and installed)\n* **Android App Runtime** (run Android applications natively...fxxking awesome)\n\n平复一下激动的心情，还是回到正题来：\n\n#### Chromium OS\n\nChrome OS is based on Chromium OS, which is the open-source development version of Chrome OS, which is a **Linux distribution** designed by Google.\n\nFor Detail, Chromium OS based on [Gentoo Linux](http://en.wikipedia.org/wiki/Gentoo_Linux), emm...\n\n"
  },
  {
    "path": "_posts/2015-04-15-os-metro.markdown",
    "content": "---\nlayout:     post\ntitle:      \"hUX 随想录（二）：操作系统的浪漫主义 —— Metro 篇\"\nsubtitle:   \"信息、载体、抽象、UI 设计乱谈\"\ndate:       2015-04-15\nauthor:     \"Hux\"\nheader-img: \"img/post-bg-os-metro.jpg\"\ncatalog: true\ntags:\n  - hUX 随想录\n  - UX/UI \n---\n\n\n> 操作系统的背后不只是冷冰冰的 0 和 1 ，数字时代的设计师们，如初神般刻画着新世界的秩序。信息、量子、宇宙，他们取世间万物为灵感来表达自己，那是它们对数字时代最浪漫的隐喻。\n\n## 前言\n\n操作系统，数字时代当之无愧的地基。当大部分从业人员都更关注它的技术与功能时，操作系统的 UI 设计师们却赋予了它无限的艺术气息：他们用充满着浪漫主义幻想色彩的设计语言，配合着物理定律般严谨的交互体系，描绘着自己心目中的数字世界，那些界面 的背后是他们对数字世界的思考、理解、期待、抽象与隐喻，**这些艺术思想支撑着浮在表面的设计**。他们用一切你熟悉或不熟悉的方式，告诉世人：  \n\n*“看呐，那个虚拟又真实的世界”*\n\n\n## Metro \n\n我们第一个要聊的，就是 [Metro](http://en.wikipedia.org/wiki/Metro_(design_language\\)) 。虽然它已经改名为 Modern UI ，虽然它作为 Windows Phone 、Windows 8 甚至 Windows 10 的 UI 风格算不上成功，但是作为一个设计语言，它却是声名显赫。以它而非 Windows 来命名这一章节，就是出于对它的敬意。\n\n![img](/img/in-post/post-metro-ui.jpg)\n\n众所周知 Metro 借鉴了交通标示语言、包豪斯现代风格与瑞士国际主义平面设计，其核心思想在于剔除多余信息，专注于内容传达（Content, not chrome），所以 Metro 采用了以 Typography、Color 为主要元素的视觉语言，另外它也非常重视动效设计（Motion Design），这是同期 UI 设计的共识，Motion provides meaning，动效对于表达隐喻有着巨大得作用。\n\n我们暂且不去讨论 Metro 在实际运用中的情况，而是尝试去猜想一下 Metro 的设计师们对数字世界的思考，以及那些隐藏在 Metro 背后的奇思妙想：\n\n#### 思考 —— 极致抽象信息\n\n数字时代是基于信息的。这也是为什么我们称这个产业为 IT (Information Technology) ，我们每天使用 PC、Mobile 等数字设备、其实本质是主动或被动的接收、筛选、消化与产生信息。\n\n语言与文字的发明是人类信息革命的第一个里程碑，掌握同种语言或文字的人类从此可以高效得进行信息的交换与传播。而现在我们正在走进人机交互与万物互联的时代：人类不但要和人类通信，还要和智能设备建立连接。历史总是上演着重复因此值得借鉴，为什么不把已经发明的东西在数字世界重新发明一次呢？**于是 Cortana 承担了微软在数字时代复刻语音的使命，而 Metro 则继承了老祖宗文字的魔力。**\n\n无论 Typography-based 还是 Content, not chrome ，**Metro 试图对一切数字时代的信息进行一种非常极致的抽象 —— 我们的 UI 不需要来自真实世界的隐喻，我们只需要足够直接的信息。** 既然文字就是信息、图片就是信息、音视频就是信息，所以它们理所当然应该直接呈现；而所有的样式也都必须直接传达信息，于是网格和灰度表示层级，颜色的存在也更多代表着符号化的视觉传达：比如用于 VI 的品牌色，或者是刻板印象心情。\n\n这种对信息简单粗暴的抽象使得 Metro 的首秀极具冲击，却也成为其日后发展最大的绊脚石。\n\n\n#### 载体 —— 信息平面\n\n信息总归需要载体，而设计师们的目的就是寻找，或者创造一种介质来承载、传递、可视化这些信息，然后呈现给用户， 最后才得以成为 UI \n\n我们都看着屏幕越来越趋于一种扁平的状态，所有设计师们理所当然的想到这种介质可能是一种类似平面的东西，比如说 WebOS 具有抽象意义的“卡片纸” ，或是 iOS/OS X 改变风格前使用的“亚麻桌布”，他们尝试告诉你藏在屏幕后面的数字世界，可能是由某种类似真实世界的平面状物体来承载信息的。  \n而 Metro 则做得更加彻底，在它看来这种拟物是强加给数字世界的不必要信息，于是它抛开了所有自然界存在的元素，又一次将信息抽象做到了极致 ：其实那就是一个单纯放置信息的平面而已，或者说，**其实是信息组成了这个平面，数字世界的信息根本无需额外的载体——文字与图像，一方面可以看作是狭义信息的载体，另一方面也可以被看作是广义信息的一种表现形态。**\n\n**所以我们可以看到 Metro UI 的背景经常是一个空旷的黑色，其实那个黑色代表着 Nothing ，意味着这个平面的下方没有任何东西。**而如果你在下方使用了图像作为背景，你就会发现这其实是两个平面 —— 上层是一个背景透明、漂浮在图像层上的信息平面。而下层则是另一个完全由图像信息组成的信息平面，当我们去划动上层时，产生的视差移动也在告诉我们：这是两个层级。\n\n![img](/img/in-post/post-metro-panorama.jpg)\n\n在所有的 Metro 组件里，我印象最深刻的叫 Panorama Panel（上图） ，Panorama 在我看来是 Metro 对信息最直接的隐喻：**不同的信息体，聚合成了一个完整的信息平面**。当我们在手机屏幕上左右滑动 Panorama 时就好像在操作一个摄像机平移镜头。这种“数字报纸”区别于报纸的最大感受就好像它可以随着信息的量级在 X 轴和 Y 轴 上无限延伸下去，变成一个信息的海洋，在你的面前流动。\n\n对啊，那不就是信息流吗。\n\n\n#### 世界 —— 卡片飞舞的世界 \n\n我之所以不愿称 Metro 的信息平面为纸片，是因为它不能卷曲也不能折叠；  \n而之所以不愿称 Metro 的信息平面为卡片，是因为它并非实体，而且尺寸无限；\n\n**可 Metro 的世界却又让我觉得是卡片飞舞的。**\n\n一张卡片的秩序是动态磁贴（Live Tiles），它很硬，只能翻转。却又具备魔力，好像在每一次的翻转中，信息都可以得到重组和再现。  \n二张卡片的秩序是视差原理（Parallax），当你移动镜头时，任意两张卡片在你眼中的位移，都必须由它们距离屏幕 (Z=0) 的深度决定  \n三张卡片的秩序就像飞来咒，原有的平面撤离，被呼唤的卡片俏皮的翻滚着从侧后方飞进视野，Metro UI 的动画设计隐喻着一切。\n\nStatus Bar 和 Application Bar 就像是紧贴在屏幕上的卡片，所以不受视差影响。而 Pivot Control 则更有魔幻色彩一点，你操纵它就如操作交通枢纽，指挥一个个小的信息片，来来去去在你的面前。\n\n所有这些零厚度的卡片，或近，或远，最终组成了整个 Metro 世界。**在我的想象里，那个次元就好像，所有的信息都以片状飞在空中，而你只能看见你所需要的那些，它们有条不紊的在纵横间穿梭，就好像到处都是信息流的交通轨道，你仿佛置身于，那个数据包飞来飞去、路由器控制地址的 —— 网路世界。**\n\n\n![img](/img/in-post/post-metro-real.jpg)\n\n\n## 结语\n\nMetro 对信息极致的抽象与压平，与同期的 iOS 6- 风格形成鲜明对比，引发大家对于数字世界与用户界面的新一轮思考，里程碑式的推动了 Flat Design 在新一代数字设计中的普及。不过我们也知道 Metro UI 在微软的实际运用中却其实不成功，这又是为什么呢？\n\n笔者抛砖引玉一些自己的观点：  \n\n当年 Metro 第一次运用在 Zune 身上时是非常惊艳的，风格超前、细节精致、动画细腻。再看现在的 Xbox （图一），Pivot 配合磁贴组、简单大气，几乎成为电视 UI 设计的模版。可偏偏在 PC 和 Mobile 两个场景，Metro 却饱受非议。\n\n在我看来 PC 和 Mobile 其实代表着两个信息密度最高的场景、PC 是传统互联网的计算中心，而 Mobile 则是移动互联网和可以预见的未来内的个人计算中心。\n**在如此复杂的场景下，其实 Metro 作为设计语言的尺度是不够的。**为什么这么说呢，虽然 Metro 对信息的抽象方式不无道理，但其实还是过分理想和纯粹了。有太多的屏幕像素因此被浪费，有太多其他维度的信息表达方式因此被舍弃掉了。\n\n也就是说：Metro 这个设计语言本身是没有问题的，但是拿目前的它作为 PC/Mobile 这种操作系统级别的设计语言却是存在问题的。**一个操作系统的设计语言与交互体系，一定不能太小，必须是一套包容性足够强又可被拓展和延伸的体系。**其实我们能看到 Windows Phone 的 UI 设计容纳度是非常低的，这或许就可以说明问题。\n\n**这也是为什么 Win 10 for PC 和 Win 10 for Mobile 都开始削弱最初的那个纯粹的 Metro 体系，转而采用一种 Metro 的视觉语言混搭非 Metro 交互逻辑的方式来设计。**\n期待 new Metro (Metro 2.0) 能在 Win 10 上逐步走向成熟，让我们一同见证。\n\n---\n\n本文是“操作系统的浪漫主义”系列的第一篇文章，如果您喜欢，请继续关注我的博客 ;)\n \n尽请期待：\n\n* **Android 篇**\n\t* 思考 —— 从卡片的层叠说起\n\t* 载体 —— 量子纸\n\t* 世界 —— 魔法材质统一世界\n\t\n* **iOS 篇**\n\t- 思考 —— 盒子里的蒸汽朋克\n\t- 载体 —— 景深的无穷近与无穷远\n\t- 世界 —— 小宇宙里的小宇宙\n\n\n"
  },
  {
    "path": "_posts/2015-05-11-see-u-ali.markdown",
    "content": "---\nlayout:     post\ntitle:      \"See you, Alibaba \"\nsubtitle:   \"再见，阿里。\"\ndate:       2015-05-11\nauthor:     \"Hux\"\nheader-img: \"img/post-bg-see-u-ali.jpg\"\ntags:\n  - Meta\n  - 阿里\n---\n\n\n> 世界那么大，我想去看看\n\nHi all  \n这里是鬼栈的离职信。\n\n![img](/img/in-post/post-c-u-ali-team.png)\n\n\n## Review\n\n去年 5 月，大二的我拿到阿里的交互实习生 Offer，成为阿里的实习员工，刚好过去一个年头。\n\n8 月，感谢 [@拔赤](http://weibo.com/jayli) 的提携，同意了我转岗到航旅前端团队的申请，分在了老大亲自带队的 **航旅事业群-无线业务部-无线技术-前端团队-前端三组**，从此开始了一名**前端程序猿**的职业生涯。\n\n我的第一个 mentor 是大家的\"小师妹\" @晴舞 姐，不过很可惜的是她居然早于我离职，回北邮任教了。我跟着她在 H5 酒店 的业务线上学习、厮杀，从一个连 git 都用不熟的小小鬼，变成了一个可以独立战斗的小鬼。  \n在 Acting H5 酒店/团购 业务线时，也非常感谢 @骏隆 的指导和信任，算是我的大半个 mentor 了。\n\n我的第二个 mentor 是人超 nice 的 @智峰 师傅，前手机腾讯网主管，负责团队 CSS 框架，很有生活哲学的一个人。我们一起拿下了 h5 红包等工作，不过很可惜的是没机会从师傅身上学更多的 CSS 了。\n\n\n有幸来阿里工作一遭，加入航旅前端团队，经历 [离线包/Hybrid容器共建](https://www.zhihu.com/question/31316032/answer/75236718) 这样的牛逼项目，负责过酒店详情、团购详情重构、个人中心红包等工作，为象声汇做过第一版 Logo、海报、颁奖证书，进行过一次团队分享[《聊聊产品与旅行》](http://huxpro.coding.me/2015/06/15/alitrip-strategy/)，更有幸认识大家。\n\n---\n\n在航旅的 270 天里，我还经历了不少**大事件**：\n\n* 一次 阿里 IPO （千载难逢的大事，可惜我没有股票，战利品是一件纪念 T 恤）\n* 一次 新品牌发布 （*阿里旅行·去啊* 的发布，BU 的大事，战利品还是一件 T 恤）\n* 一次 年会 （北京 office 第一次大规模年会，马云老陆 Lucy 悉数到场）\n* 一次 双十一 （双十一购物狂欢节，有幸从内部参与一次）\n* 一次 Outing （每年才一次的公派娱乐，滑雪＋温泉记忆深刻）\n* 一次 Team Building（晴舞姐的 lastday ，难得的团建）\n* 一次 中秋节 （战利品是包装特别用心的“马云牌”月饼）\n\n\n真的运气非常好，不但该经历的都经历了，连 IPO 这么难得的也撞上了。\n\n![img](/img/in-post/post-c-u-ali-memo.jpg)\n\n\n## To Mates\n\n感谢大家这么多天的照顾！  \n\n无线组的小伙伴们：\n\n* @拔赤：感谢老大！当年慕名而来，非常感谢“收留”\n* @虎牙：超牛的虎牙！非常佩服，人也超 nice ，一直学习的对象\n* @兰梦：大姐大！每次问问题都超级热心的解答，非常非常感谢\n* @孝瓘：大哥大！技术超牛不说了，对我超级超级好，帮我解答问题送我回家什么的，特别感动。\n* @豹子：双子座美女姐姐哈哈，前两天生日快乐哦，队花 BU 花！\n* @弘树：简直学霸 & 学神！超年轻超钻，感觉以后会是阿里前端顶梁柱人物哟\n* @若狸：猫爷！京腔儿～虽然总是在朋友圈骂 PD 不过其实特别靠谱活儿特别好哈哈哈\n* @圣耀：首页守护神！加班时你总是在，然后一起分吃，的再去苦逼干活 OTZ\n* @智峰：叶师傅！虽然总是不让我叫师兄互相学习云云，不过真的跟我说了很多人生哲理，超受用\n* @擎黄：麦霸！特别聊得来，缘分大概最早来自于坐我旁边，你和舒博搬走时我超舍不得的 T T 你还记得你拿我机箱垫脚吗！\n* @舒博：同上都是 90 后，聊得来！经常一起吃饭，Outing 的时候睡一屋，晚上打鼾完早上还会问我然后道歉特别萌哈哈哈\n* @骏隆：分不清你是哪组！不过一起共事一起玩经常一起吃饭，特别 nice，非常非常感谢，一起做酒店时非常开心！\n* @夕剑：虽然已经离职了看不到不过必须补上，机票的代名词！超 nice 超靠谱，又帅又有趣\n* @晴舞：虽然已经离职了看不到不过必须补上，特别感谢的师姐，最难的开头都是你带我走过去的\n* @已过：虽然已经转岗了看不到不过必须补上，一度觉得很像大反派！印象最深的就是刚进来 git 写错了看我 log 帮我回滚 OTZ，当时觉得特别凶\n* @清锁：实习生小伙伴！你居然先我离职了喂。不过我知道你都签三方啦\n\n其他组的我就捡比较熟悉的说啦：\n\n* @银翘：校友师姐！特别萌，负责象声汇特别尽心尽力\n* @皓勋：充满战斗力的小伙伴！超级青春洋溢，看好你哟～！\n* @懂象：Hey Flasher！Flasher 果然都爱动画爱交互，很聊得来～\n* @龙芒：好像一起打过球？哈哈哈其实没有原因就是觉得特别可爱！\n* @伯元：咦是离职了吗？一起打过好久的球！\n\n当然，还有很多前端、UED 、测试、后端、行政 的小伙伴们，就没法一一照顾到啦。\n\n**希望所有人都能工作顺利（少加班）、生活开心（多旅游）、身体健康哈。**\n\n## Future\n\n距离毕业还有一年多的光景，前路未卜，还是想到处逛逛，多看看再做选择。\n\n在陆续看了几家公司后，我决定前往**微信电影票**开始我的下一段旅程。特别巧的是，带队的饼饼居然也曾是我们团队的“老人”，花名 @痴灵 \n\n世界这么大，更要 Keep Contact. \n\n* 微博：@Hux黄玄\n* 知乎：@黄玄\n* 博客：<http://huangxuan.me>\n\n \n**Hey，这里是编号 79717**\n \n![img](/img/in-post/post-c-u-ali-079717.png)\n"
  },
  {
    "path": "_posts/2015-05-25-js-module-loader.markdown",
    "content": "---\nlayout:     post\ntitle:      \"JavaScript Module Loader\"\nsubtitle:   \"CommonJS，RequireJS，SeaJS 归纳笔记\"\ndate:       2015-05-25\nauthor:     \"Hux\"\nheader-img: \"img/post-bg-js-module.jpg\"\ncatalog: true\npublished: false\ntags:\n    - 笔记\n    - Web\n    - JavaScript\n---\n\n\n\n## Foreword\n\n> Here comes Module!\n\n随着网站逐渐变成「互联网应用程序」，嵌入网页的 JavaScript 代码越来越庞大，越来越复杂。网页越来越像桌面程序，需要一个团队分工协作、进度管理、单元测试……我们不得不使用软件工程的方法，来管理网页的业务逻辑。\n\n于是，JavaScript 的模块化成为迫切需求。在 ES6 Module 来临之前，JavaScript 社区提供了强大支持，尝试在现有的运行环境下，实现模块的效果。\n\n\n\n## CommonJS & Node\n\n> Javascript: not just for browsers any more! —— CommonJS Slogen\n\n前端模块化的事实标准之一，2009 年 8 月，[CommonJS](http://wiki.commonjs.org/wiki/CommonJS) 诞生。\n\nCommonJS 本质上只是一套规范（API 定义），而 Node.js 采用并实现了部分规范，CommonJS Module 的写法也因此广泛流行。\n\n\n让我们看看 Node 中的实现：\n\n```js\n// 由于 Node 原生支持模块的作用域，并不需要额外的 wrapper\n// \"as though the module was wrapped in a function\"\n\nvar a = require('./a')  // 加载模块（同步加载）\na.doSomething()         // 等上一句执行完才会执行\n\nexports.b = function(){ // 暴露 b 函数接口\n  // do something\n}\n```\n\n`exports`是一个内置对象，就像`require`是一个内置加载函数一样。如果你希望直接赋值一个完整的对象或者构造函数，覆写`module.exports`就可以了。\n\nCommonJS 前身叫 ServerJS ，**后来希望能更加 COMMON，成为通吃各种环境的模块规范，改名为 CommonJS** 。CommonJS 最初只专注于 Server-side 而非浏览器环境，因此它采用了同步加载的机制，这对服务器环境（硬盘 I/O 速度）不是问题，而对浏览器环境（网速）来说并不合适。\n\n\n因此，各种适用于浏览器环境的模块框架与标准逐个诞生，他们的共同点是：\n\n* 采用异步加载（预先加载所有依赖的模块后回调执行，符合浏览器的网络环境）\n* 虽然代码风格不同，但其实都可以看作 CommonJS Modules 语法的变体。\n* 都在向着 **COMMON** 的方向进化：**兼容不同风格，兼容浏览器和服务器两种环境**\n\n本文接下来要讨论的典例是：\n\n* RequireJS & AMD（异步加载，预执行，依赖前置。默认推荐 AMD 写法）\n* SeaJS & CMD（异步加载，懒执行，依赖就近，默认推荐 CommonJS 写法）\n\n\n\n\n\n## History\n\n<!--<h2 id=\"history\"> History </h2>-->\n\n> 此段落参考自玉伯的 [前端模块化开发那点历史](https://github.com/seajs/seajs/issues/588)\n\n09-10 年间，CommonJS（那时还叫 ServerJS） 社区推出 [Modules/1.0](http://wiki.commonjs.org/wiki/Modules) 规范，并且在 Node.js 等环境下取得了很不错的实践。\n\n09年下半年这帮充满干劲的小伙子们想把 ServerJS 的成功经验进一步推广到浏览器端，于是将社区改名叫 CommonJS，同时激烈争论 Modules 的下一版规范。分歧和冲突由此诞生，逐步形成了三大流派：\n\n\n1. **Modules/1.x** 流派。这个观点觉得 1.x 规范已经够用，只要移植到浏览器端就好。要做的是新增 [Modules/Transport](http://wiki.commonjs.org/wiki/Modules/Transport) 规范，即在浏览器上运行前，先通过转换工具将模块转换为符合 Transport 规范的代码。主流代表是服务端的开发人员。现在值得关注的有两个实现：越来越火的 component 和走在前沿的 es6 module transpiler。\n2. **Modules/Async** 流派。这个观点觉得浏览器有自身的特征，不应该直接用 Modules/1.x 规范。这个观点下的典型代表是 [AMD](http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition) 规范及其实现 [RequireJS](http://requirejs.org/)。这个稍后再细说。\n3. **Modules/2.0** 流派。这个观点觉得浏览器有自身的特征，不应该直接用 Modules/1.x 规范，但应该尽可能与 Modules/1.x 规范保持一致。这个观点下的典型代表是 BravoJS 和 FlyScript 的作者。BravoJS 作者对 CommonJS 的社区的贡献很大，这份 Modules/2.0-draft 规范花了很多心思。FlyScript 的作者提出了 Modules/Wrappings 规范，这规范是 CMD 规范的前身。可惜的是 BravoJS 太学院派，FlyScript 后来做了自我阉割，将整个网站（flyscript.org）下线了。这个观点在本文中的典型代表就是 SeaJS 和 CMD 了\n\n\n补一嘴：阿里 KISSY 的 KMD 其实跟 AMD 非常类似，只是用 `add`和`use` 两个源自于 YUI Modules 的函数名替换了 `define` 和 `require` ，但其原理更接近 RequireJS ，与 YUI Modules 的 `Y` 沙箱 Attach 机制并不相同\n\n\n## RequireJS & AMD\n\n[AMD (Async Module Definition)](http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition) 是 RequireJS 在推广过程中对模块定义的规范化产出。\n\n> RequireJS is a JavaScript file and module loader. It is optimized for in-browser use, but it can be used in other JavaScript environments\n\nRequireJS 主要解决的还是 CommonJS 同步加载脚本不适合浏览器 这个问题：\n\n```js\n//CommonJS\n\nvar Employee = require(\"types/Employee\");\n\nfunction Programmer (){\n    //do something\n}\n\nProgrammer.prototype = new Employee();\n\n//如果 require call 是异步的，那么肯定 error\n//因为在执行这句前 Employee 模块肯定来不及加载进来\n```\n> As the comment indicates above, if require() is async, this code will not work. However, loading scripts synchronously in the browser kills performance. So, what to do?\n\n所以我们需要 **Function Wrapping** 来获取依赖并且提前通过 script tag 提前加载进来\n\n\n```js\n//AMD Wrapper\n\ndefine(\n    [types/Employee],    //依赖\n    function(Employee){  //这个回调会在所有依赖都被加载后才执行\n\n        function Programmer(){\n            //do something\n        };\n\n        Programmer.prototype = new Employee();\n        return Programmer;  //return Constructor\n    }\n)\n```\n\n当依赖模块非常多时，这种**依赖前置**的写法会显得有点奇怪，所以 AMD 给了一个语法糖， **simplified CommonJS wrapping**，借鉴了 CommonJS 的 require 就近风格，也更方便对 CommonJS 模块的兼容：\n\n```js\ndefine(function (require) {\n    var dependency1 = require('dependency1'),\n        dependency2 = require('dependency2');\n\n    return function () {};\n});\n```\nThe AMD loader will parse out the `require('')` calls by using `Function.prototype.toString()`, then internally convert the above define call into this:\n\n```js\ndefine(['require', 'dependency1', 'dependency2'], function (require) {\n    var dependency1 = require('dependency1'),\n        dependency2 = require('dependency2');\n\n    return function () {};\n});\n```\n\n出于`Function.prototype.toString()`兼容性和性能的考虑，最好的做法还是做一次 **optimized build**\n\n\n\nAMD 和 CommonJS 的核心争议如下：\n\n### 1. **执行时机**\n\nModules/1.0:\n\n```js\nvar a = require(\"./a\") // 执行到此时，a.js 才同步下载并执行\n```\n\nAMD: （使用 require 的语法糖时）\n\n```js\ndefine([\"require\"],function(require)){\n    // 在这里，a.js 已经下载并且执行好了\n    // 使用 require() 并不是 AMD 的推荐写法\n    var a = require(\"./a\") // 此处仅仅是取模块 a 的 exports\n})\n```\n\nAMD 里提前下载 a.js 是出于对浏览器环境的考虑，只能采取异步下载，这个社区都认可（Sea.js 也是这么做的）\n\n但是 AMD 的执行是 Early Executing，而 Modules/1.0 是第一次 require 时才执行。这个差异很多人不能接受，包括持 Modules/2.0 观点的人也不能接受。\n\n### 2. **书写风格**\n\nAMD 推荐的风格并不使用`require`，而是通过参数传入，破坏了**依赖就近**：\n\n```js\ndefine([\"a\", \"b\", \"c\"],function(a, b, c){\n    // 提前申明了并初始化了所有模块\n\n    true || b.foo(); //即便根本没用到模块 b，但 b 还是提前执行了。\n})\n```\n\n不过，在笔者看来，风格喜好因人而异，主要还是**预执行**和**懒执行**的差异。\n\n另外，require 2.0 也开始思考异步处理**软依赖**（区别于一定需要的**硬依赖**）的问题，提出了这样的方案：\n\n```js\n// 函数体内：\nif(status){\n    async(['a'],function(a){\n        a.doSomething()\n    })\n}\n```\n\n## SeaJS & CMD\n\nCMD (Common Module Definition) 是 [SeaJS](http://seajs.org/docs/) 在推广过程中对模块定义的规范化产出，是 Modules/2.0 流派的支持者，因此 SeaJS 的模块写法尽可能与 Modules/1.x 规范保持一致。\n\n不过目前国外的该流派都死得差不多了，RequireJS 目前成为浏览器端模块的事实标准，国内最有名气的就是玉伯的 Sea.js ，不过对国际的推广力度不够。\n\n* CMD Specification\n    * [English (CMDJS-repo)](https://github.com/cmdjs/specification/blob/master/draft/module.md)\n    * [Chinese (SeaJS-repo)](https://github.com/seajs/seajs/issues/242)\n\n\nCMD 主要有 define, factory, require, export 这么几个东西\n\n * define `define(id?, deps?, factory)`\n * factory `factory(require, exports, module)`\n * require `require(id)`\n * exports `Object`\n\n\nCMD 推荐的 Code Style 是使用 CommonJS 风格的 `require`：\n\n* 这个 require 实际上是一个全局函数，用于加载模块，这里实际就是传入而已\n\n```js\ndefine(function(require, exports) {\n\n    // 获取模块 a 的接口\n    var a = require('./a');\n    // 调用模块 a 的方法\n    a.doSomething();\n\n    // 对外提供 foo 属性\n    exports.foo = 'bar';\n    // 对外提供 doSomething 方法\n    exports.doSomething = function() {};\n\n});\n```\n\n但是你也可以使用 AMD 风格，或者使用 return 来进行模块暴露\n\n```js\ndefine('hello', ['jquery'], function(require, exports, module) {\n\n    // 模块代码...\n\n    // 直接通过 return 暴露接口\n    return {\n        foo: 'bar',\n        doSomething: function() {}\n    };\n\n});\n```\n\n\n\nSea.js 借鉴了 RequireJS 的不少东西，比如将 FlyScript 中的 module.declare 改名为 define 等。Sea.js 更多地来自 Modules/2.0 的观点，但尽可能去掉了学院派的东西，加入了不少实战派的理念。\n\n\n\n## AMD vs CMD\n\n**虽然两者目前都兼容各种风格，但其底层原理并不相同，从其分别推荐的写法就可以看出两者背后原理的不同：**\n\n1. 对于依赖的模块，AMD 是**提前执行**，CMD 是**懒执行**。（都是先加载）\n*  CMD 推崇**依赖就近**，AMD 推崇**依赖前置**。\n\n看代码：\n\n```js\n// AMD 默认推荐\n\ndefine(['./a', './b'], function(a, b) {  // 依赖前置，提前执行\n\n    a.doSomething()\n    b.doSomething()\n\n})\n\n```\n\n```js\n// CMD\n\ndefine(function(require, exports, module) {\n\n    var a = require('./a')\n    a.doSomething()\n\n    var b = require('./b') // 依赖就近，延迟执行\n    b.doSomething()\n})\n```\n\n\n\n\n\n\n## WebPack\n\n> working...\n"
  },
  {
    "path": "_posts/2015-06-15-alitrip-strategy.markdown",
    "content": "---\nlayout:     post\ntitle:      \"聊聊「阿里旅行 · 去啊」\"\nsubtitle:   \"聊聊在线旅行行业与老东家的产品思路\"\ndate:       2015-06-15\nauthor:     \"Hux\"\nheader-img: \"img/post-bg-alitrip.jpg\"\ncatalog: true\ntags:\n    - 产品\n    - 阿里\n---\n\n## 前言\n\n近几年，互联网产品从线上斗到了线下，互联网行业和传统行业的跨界融合屡见不鲜，“渗透传统行业”几乎成为了全行业下一轮创新的标配，新词“互联网+”也应运而生：\n\n> 将互联网行业的生产要素，深度融入经济、社会等各个领域，尝试改变一些传统的实体经济行业，创造出新的产品形态、商业模式和生态\n\nO2O 领域已经有了非常多成功的案例：从最早的千团大战，到前年打车大战，再到餐饮 O2O……传统行业被撬动的同时，无数新的市场也在被发掘：\n\n* 金融： 蚂蚁金服、芝麻信用、京东白条\n* 通信： 微信电话本，阿里通信\n* 交通： 打车、租车、专车\n* 地产： 二手房、租房\n* 医疗、家电、教育、票务……\n\n当然，还有我们的在线旅游行业，BAT 纷纷入局，盛况空前。\n\n\n## 正文\n\n历史总是现在与未来的明鉴，**垂直领域互联网产品**更是与行业的历史紧密相连。想要用互联网产品解决传统行业的问题，就得先了解这个行业的发展规律，看看这个行业都经历过怎样的变革。\n\n### 传统老大：旅行社\n\n旅行社，一个耳熟能详的名字。在互联网的变革到来之前，旅游行业几乎就是旅行社的天下。\n\n在行业术语里，旅行社被称为 **TA：Travel Agency —— 旅游代理**。\n旅行社为你提供旅游信息，代理你办航班，定酒店，买门票，办签证，找导游。通过代理你的旅游消费行为，TA 从中获利。\n\n![img](/img/in-post/post-alitrip-pd/post-alitrip-pd.013.jpg)\n\n### 第一轮革命：兴起的电商与 OTA\n\n1995 年，中国互联网沸腾元年，北京上海接入 Internet 节点。\n1998 年，中国互联网电商元年，第一笔在线交易产生。\n1999 年，马云的阿里巴巴创办。同年，旅游行业未来的两大巨头，**携程**、**艺龙** 双双出世。\n\n携程、艺龙利用互联网的体验优势，迅速占领了 TA 的市场，它们被称作 **OTA：Online Travel Agency**\n\n![img](/img/in-post/post-alitrip-pd/post-alitrip-pd.014.jpg)\n\n在他们诞生之初，其实都叫 XX旅行网。那为什么不说他们是做网站的，而说他们是做 TA 的呢？\n\n这叫要引出本文涉及的第一个常见商业模式：\n\n#### Agency 模式\n\nAgency，即**代理模式**。通过代理用户的消费行为，代理商就可以靠佣金的方式从中获利。\n举个例子：假设携程旅行网今天给某某酒店拉来了 100 个日间，那么这个酒店就要以 30元/日间 的方式给携程旅行网反多少的红利。\n\n**佣金，说白了，就是中介费。**\n\n![img](/img/in-post/post-alitrip-pd/post-alitrip-pd.016.jpg)\n\n了解了 Agency 模式，我们再回过来看携程、艺龙：\n虽然渠道改成了互联网，但其商业模式还是 TA 的那套玩法，它们其实是在和传统 TA 分同一块蛋糕。\n还是咨询、酒店、机票、旅游团、旅游套餐，只是**你们在线下玩，我去线上玩了**，我有渠道优势。\n\n### 第二轮革命：比价搜索与去哪儿\n\n时光飞驰到 2005 年，单纯做线下已经满足不了很多传统 TA 们了，大家纷纷向携程、艺龙学习，进攻线上，转型 OTA 。\n\n就在这样的格局下，**去哪儿** 横空出世，一下占据了半壁江山：\n\n![img](/img/in-post/post-alitrip-pd/post-alitrip-pd.021.jpg)\n\n去哪儿做了一件什么事呢，它把这些 OTA 的数据全都爬过来，做了一个**比价平台**。这样，用户就可以在去哪儿的网站上看看哪家 OTA 更便宜，然后用户就去消费哪家的服务。\n\n所谓“比价平台”，本质上说，就是 **Search Engine —— 搜索引擎**。\n\n![img](/img/in-post/post-alitrip-pd/post-alitrip-pd.018.jpg)\n\n这个这个玩法一下就厉害了：\n**去哪儿挡在了用户和所有 OTA 之间，OTA 还是做原来的事情，而去哪儿则拿下了用户找 OTA 的过程**。同是搜索引擎的百度也是如此：百度自己并不生产内容，而是拿下了用户找内容的过程。\n\nThat's why search engine awesome：因为用户在互联网的信息海洋上找信息太难了，所以用户必须要靠搜索引擎来解决这个痛点，而搜索引擎自己也就成为了渠道：\n\n#### Channel 模式\n\nChannel，即**渠道模式**。通过优化用户的体验路径，在用户和 B 方之前挡了一道，主要对 B 盈利。\n最常见的对 B 盈利方式就是广告：**Pay For Performance**\n\n![img](/img/in-post/post-alitrip-pd/post-alitrip-pd.019.jpg)\n\n简单看一眼携程和去哪儿的收入占比就可以发现：\n\n* 携程主要靠来自酒店、机票的佣金盈利\n* 去哪儿则主要靠 PFP 广告盈利\n\n![img](/img/in-post/post-alitrip-pd/post-alitrip-pd.020.jpg)\n\n通过去哪儿的比价平台，小 OTA 开始有机会通过价格战和大 OTA 周旋。去哪儿在给予了小 OTA 机会的同时也造就了自己，这和 2003 年淘宝 C2C 的崛起，颇有异曲同工之意。\n\n\n### 第 2.5 轮革命：尴尬的淘宝旅行\n\n为什么说淘宝旅行是 2.5 次革命呢，因为它想革，但没革上。\n为什么没有革上呢？\n\n**首先是切入时机太晚**\n\n阿里其实 2010 年就开始做淘宝旅行了，一直划分在淘宝网下，由那时的淘宝北研（淘宝 UED 北京研发）团队负责，这个团队吸纳了大批雅虎中国的精英，技术水平相当高。\n可是 2010 年才切入这个市场实在是太晚了，携程、去哪儿的口碑和用户习惯早都养成好几年了，没人会去你淘宝上搜航班酒店，你有大入口也没有用。\n\n**二是资源倾斜不足**\n\n2010 年还没有什么 **互联网+** 的概念，结合传统行业也还没有现在这么热，淘宝做旅游这事用了多大力气推很难说，反正我是没听过。\n阿里同年的发展重心还是在其电商体系的完善上：**淘宝商城** 启用独立域名，其 B2C 的模式刚好弥补了淘宝 C2C 的问题，这货就是后来的**天猫**，我们可以比较一下两者在资源倾斜上的差异：\n\n\n BU | 2008 | 2010 | 2011 | 2012 | 2013 | 2014 | 2015\n---- | ------------- | ------------\n天猫 | 淘宝商城  | 独立域名 | 分拆 | 更名天猫<br>天猫事业部（1/7）|\n去啊 |  | 淘宝旅行 |  | | 航旅事业部（1/25）| 分拆<br>更名去啊 | 独立域名\n\n**三是思路问题**\n\n淘宝旅行想怎么玩呢，它实际上就是想用淘宝/天猫的思路去做在线旅行，其实背后还是淘宝卖家和天猫卖家，只不过这次的商户换成 OTA 入驻了，然后大家开开心心像卖衣服一样去卖旅行产品。\n\n\n![img](/img/in-post/post-alitrip-pd/post-alitrip-pd.023.jpg)\n\n听上去很美，不但利用了阿里系的大量资源，还直接复刻了淘宝/天猫的牛逼模式 —— 平台模式\n\n#### Platform 模式\n\nPlatform，即**平台模式**，可以说是当今最叼的商业模式了，它相当于构建了一个完整的生态、市场环境，在这里整合买卖双方的资源。通过维护市场秩序、制定市场规则，让市场活跃，从而**赚取场子费**。\n\n![img](/img/in-post/post-alitrip-pd/post-alitrip-pd.026.jpg)\n\n想想看，每一笔交易都在你的地盘上发生，只要市场一直活跃，你就可以在其中**双边、多边盈利**。什么竞价排名、广告平台、VIP 特权，盈利模式太丰富了\n\n美梦做完了，回到淘宝旅行来。做平台是每个产品的梦想，肯定是对的。那么问题出在哪呢？\n\n**太不垂直了！** 旅游行业，极度要求信誉：去哪儿对接的都是 B 类商家（OTA，品牌连锁酒店，直销等），从根本上就保证了产品体验。淘宝旅行的产品则充斥着大量的小旅行社、个人之类的小卖家，严重影响购买体验。你能想象预定一间酒店发现下面十几二十页的卖家，选完卖家又要跟人在旺旺上扯半个小时么？价格便宜作为唯一的优势，是以严重牺牲产品购买体验为代价的，极为得不偿失。更何况，旅游产品的受众大部分还是消费能力较强的人群，更是看重商家/产品质量而不是价格了。\n\n\n### 第三轮革命：Now\n\nOK，经过这么一番折腾，第三次变革就来了。\nBAT 纷纷介入，行业进入了传说中的 BATX 格局：\n\n![img](/img/in-post/post-alitrip-pd/post-alitrip-pd.028.jpg)\n\n阿里最近动作频频，力推去啊不说，更是收购线下酒店软件石基，配合蚂蚁金服期下芝麻信用开展“酒店信用住”等业务\n百度早早投资去哪儿，两个搜索引擎起家的公司风格一脉相承。同时，百度也悄悄发布了百度旅行这样的试水产品\n腾讯入股艺龙，同程网等，也在尝试 QQ 旅游等产品\n\nUpdate：不过，就在 2015.5 左右，携程宣布收购艺龙，非常戏剧性的局面啊……\n\n为什么都要介入呢？\n一是互联网结合传统行业的大潮到来，大家都发现旅游行业是一个金矿，市场其实特别大……\n二是这个领域确实还有很多可以突破的商业模式存在，很多细分领域都开始有创业公司起来，整个行业的生态也越来越丰富：\n\n![img](/img/in-post/post-alitrip-pd/post-alitrip-pd.029.jpg)\n\n这种时候，BAT 这样的土豪公司就想进来收网了 —— 砸钱也得砸出个平台来！\n所以，这一轮游戏一定能看到一次大洗牌（艺龙第一个就阵亡了）\n\n那么，这轮革命怎么演变呢？\n\n**一是模式融合**，以前做 OTA 的做 OTA，做渠道的做渠道，尝试做平台的做平台。现在，大家都知道平台模式可能是更好的形态，纷纷开始进化了。\n\n* 都做 OTA，拿下各种牛逼直营，最典型的就是航班\n* 都做平台，尤其是质量相对比较高的 B2C 平台。然后尝试可能的 C2C 产品形态 （去啊的客栈是一个很好的尝试）\n\n![img](/img/in-post/post-alitrip-pd/post-alitrip-pd.030.jpg)\n\n**二是思路进化**\n\n* 从单一的购买/渠道业务转向服务平台。融合周边服务，拉上细分领域，外围行业一起玩\n* 强调用户体验与用户留存，强调**一站式服务**、**个性化服务** 等更极致的产品形态\n\n![img](/img/in-post/post-alitrip-pd/post-alitrip-pd.031.jpg)\n\n\n而这些演变，正是 **阿里旅行 · 去啊** 致力去做到的。从大版本 5.0 开始，淘宝旅行将 **洗心革面**，去追求一个更极致，更垂直，体验更优秀的产品形态。\n\n让我们一起见证去啊的成长，与在线旅游行业的变革吧！\n\n\n---\n\n*本篇完。*\n\n\n\n> 本文作者系前「阿里旅行 · 去啊」前端实习生，本文系业余时间学习之作。\n> 如有任何知识产权、版权问题或理论错误，还请指正。\n> 转载请注明原作者及以上信息。\n"
  },
  {
    "path": "_posts/2015-07-09-js-module-7day.markdown",
    "content": "---\nlayout:     keynote\ntitle:      \"JavaScript 模块化七日谈\"\nsubtitle:   \"🎞  Slides:JavaScript Modularization Journey\"\niframe:     \"//huangxuan.me/js-module-7day/\"\ndate:       2015-07-09\nauthor:     \"Hux\"\ntags:\n    - Slides\n    - Web\n    - JavaScript\n---\n\n\n> 下滑这里查看更多内容\n\n7月9日，我在公司内部进行了名为「JavaScript 模块化七日谈」分享，并将该 Slides 分享到了微博上。出乎意料地，这篇微博先后被 @JS小组 @尤小右 @寸志 等近 200 人转发，阅读达到 10w，获得了还不错的评价。\n\n于是，我决定将它重新发到我的博客上，并为它专门制作了适用于 Keynote 展示文稿的新布局。它能自动根据屏幕大小/旋转以一定比例填充屏幕，你也可以直接点击下方链接在新页面打开，来获得更好的、沉浸式的全屏体验\n\n\n### [Watch Fullscreen →](https://huangxuan.me/js-module-7day/)\n\n<div class=\"visible-md visible-lg\">\n<img src=\"//huangxuan.me/js-module-7day/attach/qrcode.png\" width=\"350\"/>\n<small class=\"img-hint\">你也可以通过扫描二维码在手机上观看</small>\n</div>\n\n\n这个 Web Slides 开源在[我的 Github 上](https://github.com/Huxpro/js-module-7day)，欢迎你帮助我完善这个展示文稿，你可以给我提 issue，可以 fork & pull request。如果它能帮助到你了，希望你还能不吝啬 star 一下这个项目\n\n\n### Catalog\n\n- 第一日 上古时期 ***Module?*** 从设计模式说起\n- 第二日 石器时代 ***Script Loader*** 只有封装性可不够，我们还需要加载\n- 第三日 蒸汽朋克 ***Module Loader*** 模块化架构的工业革命\n- 第四日 号角吹响 ***CommonJS*** 征服世界的第一步是跳出浏览器\n- 第五日 双塔奇兵 ***AMD/CMD*** 浏览器环境模块化方案\n- 第六日 精灵宝钻 ***Browserify/Webpack*** 大势所趋，去掉这层包裹！\n- 第七日 王者归来 ***ES6 Module*** 最后的战役\n\n### Thanks\n\n[Reveal.js](http://lab.hakim.se/reveal-js)\n"
  },
  {
    "path": "_posts/2015-09-22-js-version.markdown",
    "content": "---\nlayout:     post\ntitle:      \"「译」ES5, ES6, ES2016, ES.Next: JavaScript 的版本是怎么回事？\"\nsubtitle:   \"ES5, ES6, ES2016, ES.Next: What's going on with JavaScript versioning?\"\ndate:       2015-09-22\nauthor:     \"Hux\"\nheader-img: \"img/post-bg-js-version.jpg\"\ntags:\n    - Web\n    - JavaScript\n    - 译\n---\n\n\nJavaScript 有着很奇怪的命名史。\n\n1995 年，它作为网景浏览器（Netscape Navigator）的一部分首次发布，网景给这个新语言命名为 LiveScript。一年后，为了搭上当时媒体热炒 Java 的顺风车，临时改名为了 JavaScript *（当然，Java 和 JavaScript 的关系，就和雷锋和雷锋塔一样 —— 并没有什么关系）*\n\n![java-javascript](/img/in-post/post-js-version/javascript-java.jpg)\n<small class=\"img-hint\">歪果仁的笑话怎么一点都不好笑</small>\n\n> 译者注：[wikipedia 的 JavaScript 词条](https://en.wikipedia.org/wiki/JavaScript#History) 更详细的叙述了这段历史\n\n1996 年，网景将 JavaScript 提交给 [ECMA International（欧洲计算机制造商协会）](http://www.ecma-international.org/) 进行标准化，并最终确定出新的语言标准，它就是 ECMAScript。自此，ECMAScript 成为所有 JavaScript 实现的基础，不过，由于 JavaScript 名字的历史原因和市场原因（很显然 ECMAScript 这个名字并不令人喜欢……），现实中我们只用 ECMAScript 称呼标准，平时都还是使用 JavaScript 来称呼这个语言。\n\n\n> 术语（译者注）：\n> \n> * *标准（Standard）*： 用于定义与其他事物区别的一套规则\n> * *实现（Implementation）*： 某个标准的具体实施/真实实践\n\n\n不过，JavaScript 开发者们并不怎么在乎这些，因为在诞生之后的 15 年里，ECMAScript 并没有多少变化，而且现实中的很多实现都已经和标准大相径庭。其实在第一版的 ECMAScript 发布后，很快又跟进发布了两个版本，但是自从 1999 年 ECMAScript 3 发布后，十年内都没有任何改动被成功添加到官方规范里。取而代之的，是各大浏览器厂商们争先进行自己的语言拓展，web 开发者们别无选择只能去尝试并且支持这些 API。即使是在 2009 年 ECMAScript 5 发布之后，仍然用了数年这些新规范才得到了浏览器的广泛支持，可是大部分开发者还是写着 ECMAScript 3 风格的代码，并不觉得有必要去了解这些规范。\n\n> 译者注：[ECMAScript 第四版草案](https://en.wikipedia.org/wiki/ECMAScript#4th_Edition_.28abandoned.29)由于太过激进而被抛弃，Adobe 的 [ActionScript 3.0](https://en.wikipedia.org/wiki/ActionScript) 是 ECMAScript edition 4 的唯一实现（ Flash 差点就统一 Web 了）\n\n到了 2012 年，事情突然开始有了转变。大家开始推动停止对旧版本 IE 浏览器的支持，用 ECMAScript 5 (ES5) 风格来编写代码也变得更加可行。与此同时，一个新的 ECMAScript 规范也开始启动。到了这时，大家开始逐渐习惯以对 ECMAScript 规范的版本支持程度来形容各种 JavaScript 实现。在正式被指名为 ECMAScript 第 6 版 (ES6) 之前，这个新的标准原本被称为 ES.Harmony（和谐）。2015 年，负责制定 ECMAScript 规范草案的委员会 TC39 决定将定义新标准的制度改为一年一次，这意味着每个新特性一旦被批准就可以添加，而不像以往一样，规范只有在整个草案完成，所有特性都没问题后才能被定稿。因此，ECMAScript 第 6 版在六月份公布之前，又被重命名为了 ECMAScript 2015（ES2015）\n\n目前，仍然有很多新的 JavaScript 特性或语法正在提议中，包括 [decorators（装饰者）](https://github.com/wycats/javascript-decorators)，[async-await（async-await 异步编程模型）](https://github.com/lukehoban/ecmascript-asyncawait) 和 [static class properties（静态类属性）](https://github.com/jeffmo/es-class-properties)。它们通常被称为 ES7，ES2016 或者 ES.Next 的特性，不过实际上它们只能被称作提案或者说可能性，毕竟 ES2016 的规范还没有完成，有可能全部都会引入，也有可能一个都没有。TC39 把一个提案分为 4 个阶段，你可以在 [Babel 的官网](https://babeljs.io/docs/usage/experimental/) 上查看各个提案目前都在哪个阶段了。\n\n所以，我们该如何使用这一大堆术语呢？下面的列表或许能帮助到你：\n\n* **ECMAScript**：一个由 ECMA International 进行标准化，TC39 委员会进行监督的语言。通常用于指代标准本身。\n* **JavaScript**：ECMAScript 标准的各种实现的最常用称呼。这个术语并不局限于某个特定版本的 ECMAScript 规范，并且可能被用于任何不同程度的任意版本的 ECMAScript 的实现。\n* **ECMAScript 5 (ES5)**：ECMAScript 的第五版修订，于 2009 年完成标准化。这个规范在所有现代浏览器中都相当完全的实现了。\n* **ECMAScript 6 (ES6) / ECMAScript 2015 (ES2015)**：ECMAScript 的第六版修订，于 2015 年完成标准化。这个标准被部分实现于大部分现代浏览器。可以查阅[这张兼容性表](http://kangax.github.io/compat-table/es6/)来查看不同浏览器和工具的实现情况。\n* **ECMAScript 2016**：预计的第七版 ECMAScript 修订，计划于明年夏季发布。这份规范具体将包含哪些特性还没有最终确定\n* **ECMAScript Proposals**：被考虑加入未来版本 ECMAScript 标准的特性与语法提案，他们需要经历五个阶段：Strawman（稻草人），Proposal（提议），Draft（草案），Candidate（候选）以及 Finished （完成）。\n\n在这整个 Blog 中，我将把目前的 ECMAScript 版本称作 ES6（因为这是大部分开发者最习以为常的），把明年的规范称作 ES2016（因为，与 ES6/ES2015 不同，这个名字将在整个标准化过程中沿用）并且将那些还没有成为 ECMAScript 定稿或草案的未来语言概念称为 ECMAScript 提案或者 JavaScript 提案。我将尽我所能在任何可能引起困惑的场合沿用这篇文章。\n\n#### 一些资源\n\n  \n\n* TC39 的 [Github 仓库](https://github.com/tc39/ecma262)上可以看到所有目前公开的提案\n* 如果你还不熟悉 ES6，Babel 有一个[很不错的特性概览](https://babeljs.io/docs/learn-es2015/)\n* 如果你希望深入 ES6，这里有两本很不错的书： Axel Rauschmayer 的 [Exploring ES6](http://exploringjs.com/)和 Nicholas Zakas 的 [Understanding ECMAScript 6](https://leanpub.com/understandinges6)。Axel 的博客 [2ality](http://www.2ality.com/) 也是很不错的 ES6 资源\n\n<img class=\"shadow\" width=\"320\" src=\"/img/in-post/post-js-version/keep-calm-and-learn-javascript.png\" />\n<small class=\"img-hint\">来学 JavaScript 吧！</small>\n\n#### 著作权声明\n\n本文译自 [ES5, ES6, ES2016, ES.Next: What's going on with JavaScript versioning?](http://benmccormick.org/2015/09/14/es5-es6-es2016-es-next-whats-going-on-with-javascript-versioning/)   \n译者 [黄玄](http://weibo.com/huxpro)，首次发布于 [Hux Blog](http://huangxuan.me)，转载请保留以上链接\n\n"
  },
  {
    "path": "_posts/2015-10-28-how-designer-learn-fe.markdown",
    "content": "---\nlayout:     post\ntitle:      \"设计师如何学习前端？\"\nsubtitle:   \"How designers learn front-end development?\"\ndate:       2015-10-28 12:00:00\nauthor:     \"Hux\"\nheader-img: \"img/home-bg-o.jpg\"\ntags:\n    - 知乎\n    - Web\n    - UX/UI\n---\n\n> 这篇文章转载自[我在知乎上的回答](https://www.zhihu.com/question/21921588/answer/69680480)，也被刊登于[优秀网页设计](http://www.uisdc.com/head-first-front-end)等多个网站上 ;)\n\n\n笔者的经历在知乎就可以看到，大学专业是数字媒体艺术，大一实习过动效设计师，大二拿到了人生第一个大公司 offer 是阿里的交互设计，后来转岗到淘宝旅行的前端团队，现在在微信电影票做前端研发。\n<br>\n<br>也是走过了不少野路子，不过还好有小右哥 <a data-hash=\"cfdec6226ece879d2571fbc274372e9f\" href=\"//www.zhihu.com/people/cfdec6226ece879d2571fbc274372e9f\" class=\"member_mention\" data-editable=\"true\" data-title=\"@尤雨溪\" data-tip=\"p$b$cfdec6226ece879d2571fbc274372e9f\">@尤雨溪</a> 这样艺术/设计转前端的大神在前面做典范，也证明这条路是玩的通的 ;)\n<br>\n<br>接下来就说说自己的学习建议吧，一个小教程，也是自己走过的流程，仅供参考哈\n<br>\n<br>------------\n<br>\n<br><b>背景篇</b>\n<br>\n<br>在这个时代学习新东西，一定要善于使用 Bing/Google 等搜索引擎…网络上的资源非常丰富，自学能力也尤为重要，尤其是对于学习技术！\n<br>\n<br>\n<br>\n<br><b>入门篇（HTML/CSS）</b>\n<br>\n<br>说起设计师希望学前端的初衷，大概还是因为各种华丽的网页特效/交互太过吸引人，这种感觉大概就是：“Hey，我的设计可以做成网页访问了呢！”\n<br>好在，“展示”对于前端技术来说反而是最简单的部分。所以，放下你对“编程”两个字的恐惧，<b>从“称不上是编程语言”的 HTML/CSS 开始，先做点有成就感的东西出来吧！</b>\n<br>\n<br>对于设计师来说，最有成就感的一定是“可以看到的东西”，而 HTML/CSS 正是用来干这个的，HTML 就是一堆非常简单的标签，而 CSS 无非就是把你画画的流程用<b>英语</b>按一定的格式写出来而已：\n<br>\n\n\n```html\n<p> p is paragraph! </p>\n\n<style>\np { color: red;}\n</style>\n```\n\n\n是不是非常容易，就跟读英语一样！\n<br>接下来，你就需要开始自学啦，比如常用 HTML 标签的意思，各种 CSS 的属性，还有 CSS 的盒模型、优先级、选择器……放心，它们都很容易；能玩得转 PS/AI/Flash/Axure/AE/Sketch 的设计师们，学这个洒洒水啦\n<br>\n<br>推荐几个资源：\n<br>\n<ul>\n    <li><a href=\"//link.zhihu.com/?target=http%3A//www.w3school.com.cn/\" class=\" wrap external\" target=\"_blank\" rel=\"nofollow noreferrer\">w3school 在线教程<i class=\"icon-external\"></i></a> (中文，一个很 Low 但是又很好的入门学习网站）\n        <br>\n    </li>\n    <li><a href=\"//link.zhihu.com/?target=http%3A//www.codecademy.com/\" class=\" wrap external\" target=\"_blank\" rel=\"nofollow noreferrer\">Learn to code<i class=\"icon-external\"></i></a> (Codecademy，如果你英文 OK，<b>强烈建议</b>你使用它进行交互式的学习！里面从 HTML/CSS 到搭建网站的课程都有，免费，生动直观）\n        <br>\n    </li>\n</ul>\n<br><b>这个阶段的练习主要是“临摹”：用代码画出你想画的网站，越多越好。</b>\n<br>\n<br>对于书，我<b>非常不推荐</b>上来就去看各种厚厚的入门/指南书，没必要！这一个阶段应该快速上手，培养兴趣，培养成就感。先做出可以看的东西再说，掌握常用的 HTML/CSS 就够用了\n<br>\n<br>如果完成的好，这个阶段过后你大概就可以写出一些简单又好看的“静态网页”了，比如这个作品集/简历：<a href=\"//link.zhihu.com/?target=http%3A//huangxuan.me/portfolio/\" class=\" wrap external\" target=\"_blank\" rel=\"nofollow noreferrer\">Portfolio - 黄玄的博客<i class=\"icon-external\"></i></a> （好久没更新了…丢人现眼）\n<br>\n<br>\n<br>\n<br><b>入门篇（JavaScript/jQuery）</b>\n<br>\n<br>想要在网页上实现一些交互效果，比如轮播图、点击按钮后播放动画？那你就必须要开始学习 JavaScript 了！JavaScript 是一门完整、强大并且非常热门的编程语言，你在浏览器里看到的所有交互或者高级功能都是由它在背后支撑的！\n<br>\n<br>举个小栗子：\n<br>\n\n```js\nalert(\"Hello World!\")\n```\n\n就这一行，就可以在浏览器里弹出 Hello World 啦！\n<br>\n<br>在了解一些基础的 JavaScript 概念（变量、函数、基本类型）后，我们可以直接去学习 jQuery，你不用知道它具体是什么（它是一个 JavaScript 代码库），你只要知道它可以显著地降低你编写交互的难度就好了：\n<br>\n\n```js\n$('.className').click(function(){\n  alert(\"Hello jQuery\")\n})\n```\n\n通过 jQuery，我们可以继续使用在 CSS 中学到的“选择器”\n<br>\n<br>对于没有编程基础的人来说，想要完全掌握它们两并不容易。作为设计师，很多时候我们可以先不必深究它们的原理，而是尝试直接应用它！这样成就感会来得很快，并且你可以通过实际应用更加理解 JavaScript 是用来做什么的。\n<br>\n<br>我仍然推荐你使用 <a href=\"http://www.w3school.com.cn/\" target=\"_blank\" rel=\"nofollow noreferrer\">w3school 在线教程</a> 与 <a href=\"//www.codecademy.com/\" target=\"_blank\" >http://www.codecademy.com/</a> 进行学习。另外，你可以看一看诸如《<a href=\"//link.zhihu.com/?target=http%3A//book.douban.com/subject/10792216/\" class=\" wrap external\" target=\"_blank\" rel=\"nofollow noreferrer\">锋利的jQuery (豆瓣)<i class=\"icon-external\"></i></a>》 这一类非常实用的书籍，可以让你很快上手做出一些简单的效果来！\n<br>\n<br>如果学习得顺利，你还可以尝试使用各种丰富的 jQuery 插件，你会发现写出支持用户交互的网站也没有那么困难～很多看上去很复杂的功能（比如轮播图、灯箱、下拉菜单），搜一搜然后看看文档（教程）、改改示例代码就好了。\n<br>\n<br>比如说，配合 <a href=\"//link.zhihu.com/?target=https%3A//github.com/Huxpro/jquery.HSlider\" class=\" wrap external\" target=\"_blank\" rel=\"nofollow noreferrer\">Huxpro/jquery.HSlider · GitHub<i class=\"icon-external\"></i></a> 这样的轮播图插件，你可以很轻松的写出 <a href=\"//link.zhihu.com/?target=http%3A//huangxuan.me/jquery.HSlider/\" class=\" wrap external\" target=\"_blank\" rel=\"nofollow noreferrer\">HSlider | Demo<i class=\"icon-external\"></i></a> 这样的网页相册或者 <a href=\"//link.zhihu.com/?target=http%3A//huangxuan.me/jquery.HSlider/demo-weather-app/\" class=\" wrap external\" target=\"_blank\" rel=\"nofollow noreferrer\">HSlider | Weather<i class=\"icon-external\"></i></a> 这样的手机端 App 原型～\n<br>\n<br>最后，我想推荐下 <a href=\"//link.zhihu.com/?target=http%3A//getbootstrap.com/\" class=\" wrap external\" target=\"_blank\" rel=\"nofollow noreferrer\">Bootstrap · The world's most popular mobile-first and respons<i class=\"icon-external\"></i></a> ，这是世界上最知名的前端 UI 框架之一，提供了大量 CSS 样式与 jQuery 插件。它非常容易学习并且中英文教程都非常健全，你并不需要理解它背后的工作原理就能很好的使用它，让你快速达到“可以建站的水平”。有余力的话，你不但可以学习如何使用它，还可以学习它背后的设计思想。\n<br>\n<br>\n<br>\n<br><b>转职方向一：前端重构 （Web Rebuild）</b>\n<br>\n<br>业内通常把专精 HTML/CSS 的前端从业人员称为重构，而对于注重视觉效果的设计师来说，在掌握基本的 HTML/CSS 后，就可以朝着这个方向发展了。\n<br>\n<br><b>到了这个阶段，你不但要知道怎么写页面，还要知道它们都是为什么，并且知道怎么做更好。这对你理解 Web 世界非常有帮助，并且能帮助你做出更“系统化”的设计。</b>\n<br>\n<br>CSS 的学问很多，你需要开始理解文档流、浮动流等各种定位的方式与原理，理解 CSS 的继承复用思想、理解浏览器的差异、兼容、优雅降级……这里强烈推荐一本书：《<a href=\"//link.zhihu.com/?target=http%3A//book.douban.com/subject/4736167/\" class=\" wrap external\" target=\"_blank\" rel=\"nofollow noreferrer\">精通CSS（第2版） (豆瓣)<i class=\"icon-external\"></i></a>》，虽然前端技术突飞猛进，但这本书的思想永远不会过时。\n<br>\n<br>HTML 方面，要开始注重语义化、可访问性与结构的合理，你要开始学习“结构与样式的分离”，这里有一本神书将这种分离做到了极致：《<a href=\"//link.zhihu.com/?target=http%3A//book.douban.com/subject/2052176/\" class=\" wrap external\" target=\"_blank\" rel=\"nofollow noreferrer\">CSS禅意花园 (豆瓣)<i class=\"icon-external\"></i></a>》\n<br>\n<br>另外，各种炫酷屌的 CSS 3 属性你一定会喜欢：你可以用媒体查询做响应式网页设计，你可以用 transiton 和 animation 做补间动画与关键帧动画，用 transform 做缩放、旋转、3D变换，还有圆角、渐变、阴影、弹性盒！样样都是设计师的神器！\n<br>\n<br>如果你还掌握了 <b>入门篇（JavaScript/jQuery）</b>的知识，那么<b>恭喜你！你已经可以做出很多有趣的网页了！</b>很多 minisite 或者微信上的“H5” 小广告，这个程度的你已经可以轻松完成了！\n<br>\n<br>配合上你的设计功力，你可以开始尝试创作一些好玩的东西，比如这种富含交互和动画的网站 <a href=\"//link.zhihu.com/?target=http%3A//huangxuan.me/senova/\" class=\" wrap external\" target=\"_blank\" rel=\"nofollow noreferrer\">绅宝 SENOVA<i class=\"icon-external\"></i></a> ，它仍然是基于 <a href=\"//link.zhihu.com/?target=https%3A//github.com/Huxpro/jquery.HSlider\" class=\" wrap external\" target=\"_blank\" rel=\"nofollow noreferrer\">Huxpro/jquery.HSlider · GitHub<i class=\"icon-external\"></i></a> 实现的！或者给自己做个小小的个人网站试试\n<br>\n<br>\n<br>\n<br><b>转职方向二：前端工程师（Front-end Engineer）</b>\n<br>\n<br>如果你觉得上述的这些都还满足不了你，你渴望做出更多了不起的交互，甚至你已经喜欢上了编程，想要转行做工程师，或者成为一名全栈设计师，那么你可以朝着这个方向继续发展！\n<br>\n<br>这个阶段的最大难度，是你必须<b>学会像一名软件工程师一样思考</b>。你需要踏踏实实学习编程语言，深入理解作用域、对象、类、封装、继承、面向对象编程、事件侦听、事件冒泡等一大堆编程概念，你还需要了解浏览器，学习 DOM、BOM、CSSOM 的 API，你甚至还需要学习一些网络原理，包括域名、URL、DNS、HTTP 请求都是什么…\n<br>\n<br>你可能会被这一大堆名词吓到。确实，想要搞定他们并不容易。但是，你要相信只要你肯花功夫它们也没有那么难，而更重要的是，如果你能拿下他们，你所收获的并不只是这些而已，而是真正跨过了一道大坎 —— <b>你的世界将因此打开， 你看待世界的方式将因此改变</b>\n<br>\n<br>对于这个阶段，你可以继续在 <a href=\"//www.codecademy.com/\" target=\"_blank\" >http://www.codecademy.com/</a> 上学习，但是 w3school 已经不够用了，遇到不会的语法，我推荐你查阅 <a href=\"//link.zhihu.com/?target=https%3A//developer.mozilla.org/zh-CN/\" class=\" wrap external\" target=\"_blank\" rel=\"nofollow noreferrer\">Mozilla 开发者网络<i class=\"icon-external\"></i></a>，这是少数中英文都有的非常专业且友好的网站。\n<br>\n<br>同时，你可能需要看一些书本来帮助你学习 JavaScript ：\n<br>\n<ul>\n    <li> 《<a href=\"//link.zhihu.com/?target=http%3A//book.douban.com/subject/10546125/\" class=\" wrap external\" target=\"_blank\" rel=\"nofollow noreferrer\">JavaScript高级程序设计（第3版） (豆瓣)<i class=\"icon-external\"></i></a> 》或 《<a href=\"//link.zhihu.com/?target=http%3A//book.douban.com/subject/2228378/\" class=\" wrap external\" target=\"_blank\" rel=\"nofollow noreferrer\">JavaScript权威指南 (豆瓣)<i class=\"icon-external\"></i></a>》，大而全的书只需要一本就够了</li>\n    <li>如果上面这本你觉得太难，你可以先看 《<a href=\"//link.zhihu.com/?target=http%3A//book.douban.com/subject/6038371/\" class=\" wrap external\" target=\"_blank\" rel=\"nofollow noreferrer\">JavaScript DOM编程艺术 （第2版） (豆瓣)<i class=\"icon-external\"></i></a>》来过渡一下，这本书比较容易，它会教给你 “优雅降级、渐进增强”的优秀思想</li>\n</ul>\n<br>如果你能顺利得渡过了这个阶段，我想你已经能做出很多令你自豪的网站了！试着向身边的工程师朋友询问如何购买域名、配置简单的静态服务器，或者搜搜“Github Pages”，然后把你的作品挂在网络上让大家欣赏吧！\n<br>\n<br>你还可以试着用 JavaScript 写写小游戏，这不但能锻炼你的编程水平还非常有趣～比如这是我刚学 JS 不久后 hack 一晚的产物 —— 用 DOM 实现的打飞机：<a href=\"//link.zhihu.com/?target=http%3A//huangxuan.me/aircraft\" class=\" wrap external\" target=\"_blank\" rel=\"nofollow noreferrer\">Hux - Aircraft<i class=\"icon-external\"></i></a> （不支持手机）\n<br>\n<br>\n<br>\n<br><b>入行篇</b>\n<br>\n<br>如果你能完成上述所有的学习，你已经是一名非常出色的前端学徒了！对于只是想要丰富技能的设计师或者产品经理来说，接下来的内容可能会让你感到不适 ;(\n<br>但如果你铁了心想要真正入行进入大公司从事专职前端开发的工作，那么你可以接着往下看：\n<br>\n<br>近几年的前端技术发展迅猛，前端工程师早已不是切切图写写页面做点特效就完事的职位，你需要具备相当完善的工程师素质与计算机知识，成为一名真正的工程师。\n<br>\n<br><b>你需要非常了解 JavaScript 这门语言</b>，包括 闭包、IIFE、this、prototype 及一些底层实现（ES、VO、AO）、熟悉常用的设计模式与 JavaScript 范式（比如实现类与私有属性）。另外，新的 ES6 已经问世，包括 class, module, arrow function 等等\n<br>\n<br><b>你需要非常了解前端常用的网络及后端知识</b>，包括 Ajax、JSON、HTTP 请求、GET/POST 差异、RESTful、URL hash/query、webSocket、常用的跨域方式（JSONP/CORS、HTTP 强缓存/协商缓存，以及如何利用 CDN 、静态网站/动态网站区别、服务器端渲染/前端渲染区别等等\n<br>\n<br><b>你需要学习使用进阶的 CSS</b>，包括熟悉 CSS 3，使用 Scss/Less 等编译到 CSS 的语言，使用 autoprefixer 等 PostCSS 工具，了解 CSS 在 Scope/Namespace 上的缺陷，你还可以学习 CSS Modules、CSS in JS 这些有趣的新玩意\n<br>\n<br><b>你需要非常了解前端的模块化规范</b>，可能在你学习到这里的时候，Require.js/AMD 已经再见了，但是 CommonJS 与 ES6 Modules 你必须要了解。（你可以观看我的分享《<a href=\"//link.zhihu.com/?target=http%3A//huangxuan.me/js-module-7day/%23/\" class=\" wrap external\" target=\"_blank\" rel=\"nofollow noreferrer\">JavaScript Modularization Seven Day<i class=\"icon-external\"></i></a>》 来学习 JS 模块化的历史）\n<br>\n<br><b>你需要熟悉 Git 与 Shell 的使用</b>，包括基于 git 的版本管理、分支管理与团队协作，包括简单的 Linux/Unix 命令、你要知道大部分程序员的工作可以通过 shell 更快更酷的完成，并且很多“软件”只能通过 shell 来使用。你还可以把你的代码放到 github 上与人分享，并且学习 github 上其他优秀的开源代码\n<br>\n<br><b>你需要熟悉并且习惯使用 Node</b>，包括了解 npm、使用 Grunt/Gulp/Browserify/Webpack 优化你的工作流、对你的代码进行打包、混淆、压缩、发布，你还可以使用 Express/Koa 配合 MongoDB/Redis 涉足到后端领域，或者尝试用 Node 做后端渲染优化你的首屏体验\n<br>\n<br><b>你需要了解各种 HTML 5 的新 API</b>，包括 &lt;video&gt;/&lt;audio&gt;，包括 Canvas，webGL、File API、App Cache、localStorage、IndexedDB、Drag &amp; Drop、更高级的 DOM API、Fetch API 等等\n<br>\n<br><b>你需要学习 JavaScript 的单线程与异步编程方法</b>，因为它们非常非常常用、包括 setTimeout/setInterval，回调与回调地狱、事件与event loop、还有 Promise 甚至 Async/Await\n<br>\n<br><b>你需要非常了解浏览器</b>，包括主流浏览器的名称、内核与差异、包括私有属性与 -webkit- 等厂商前缀，你需要学习如何使用 Chrome DevTool，你需要了解浏览器渲染的 reflow/repaint 来避免 Jank 并进行有针对性的性能优化\n<br>\n<br><b>你需要专门学习 Mobile Web</b>，因为移动互联网是趋势。包括 viewport、CSS pixel、 touch 事件、iOS/Android 浏览器的差异与兼容、移动端的性能优化、300ms delay 等等…你还需要知道 Hybrid 是什么，包括 Cordova/Phonegap，更复杂的比如和 iOS/Android 通信的机制，比如 URI Scheme 或者 JS Bridge\n<br>\n<br><b>你需要学习一些</b><b>非常火热的前端框架/库</b>，他们不但能帮助你更快的进行开发、更重要的是他们背后所蕴含的思想。包括 Backbone、Angular、Vue、React、Polymer 等等、了解它们背后的双向数据绑定、单向数据流、MVC/MVVM/Flux 思想、Web Component 与组件化等等\n<br>\n<br><b>你需要学习如何构建 web 单页应用</b>，这是 web 的未来，包括利用 history API 或者 hash 实现路由，包括基于 Ajax + 模版引擎或者其他技术的前端渲染、包括组织较为复杂的软件设计等等\n<br>\n<br><b>我还建议你学习更多的计算机知识</b>，它们能对你的代码能起到潜移默化的作用，包括简单的计算机体系结构、更广泛的编程知识（面向对象/函数式等）、栈、堆、数组、队列、哈希表、树、图等数据结构、时间复杂度与空间复杂度以及简单的算法等等\n<br>\n<br><b>你需要了解业内的大神并阅读它们的博客/知乎/微博</b>，比如 <a data-hash=\"cfdec6226ece879d2571fbc274372e9f\" href=\"//www.zhihu.com/people/cfdec6226ece879d2571fbc274372e9f\" class=\"member_mention\" data-editable=\"true\" data-title=\"@尤雨溪\" data-tip=\"p$b$cfdec6226ece879d2571fbc274372e9f\">@尤雨溪</a><a data-hash=\"3ec3b166992a5a90a1083945d2490d38\" href=\"//www.zhihu.com/people/3ec3b166992a5a90a1083945d2490d38\" class=\"member_mention\" data-editable=\"true\" data-title=\"@贺师俊\" data-tip=\"p$b$3ec3b166992a5a90a1083945d2490d38\">@贺师俊</a><a data-hash=\"3212f9044005e9306aab1b61e74e7ae6\" href=\"//www.zhihu.com/people/3212f9044005e9306aab1b61e74e7ae6\" class=\"member_mention\" data-editable=\"true\" data-title=\"@张云龙\" data-tip=\"p$b$3212f9044005e9306aab1b61e74e7ae6\">@张云龙</a><a data-hash=\"c5198d4e9c0145aee04dd53cc6590edd\" href=\"//www.zhihu.com/people/c5198d4e9c0145aee04dd53cc6590edd\" class=\"member_mention\" data-editable=\"true\" data-title=\"@徐飞\" data-tip=\"p$b$c5198d4e9c0145aee04dd53cc6590edd\">@徐飞</a><a data-hash=\"20fdd386a6e59d178b8fe14e2863cb40\" href=\"//www.zhihu.com/people/20fdd386a6e59d178b8fe14e2863cb40\" class=\"member_mention\" data-editable=\"true\" data-title=\"@张克军\" data-tip=\"p$b$20fdd386a6e59d178b8fe14e2863cb40\">@张克军</a><a data-hash=\"c11336b8607d86bc9090bed90757a34c\" href=\"//www.zhihu.com/people/c11336b8607d86bc9090bed90757a34c\" class=\"member_mention\" data-editable=\"true\" data-title=\"@玉伯\" data-tip=\"p$b$c11336b8607d86bc9090bed90757a34c\">@玉伯</a><a data-hash=\"64458d15a75902cd0425732b7b757705\" href=\"//www.zhihu.com/people/64458d15a75902cd0425732b7b757705\" class=\"member_mention\" data-editable=\"true\" data-title=\"@拔赤\" data-tip=\"p$b$64458d15a75902cd0425732b7b757705\">@拔赤</a><a data-hash=\"0d9b98af12015c94cff646a6fc0773b5\" href=\"//www.zhihu.com/people/0d9b98af12015c94cff646a6fc0773b5\" class=\"member_mention\" data-editable=\"true\" data-title=\"@寸志\" data-tip=\"p$b$0d9b98af12015c94cff646a6fc0773b5\">@寸志</a><a data-hash=\"790dccce26904cdcd11b0fad3bac37b7\" href=\"//www.zhihu.com/people/790dccce26904cdcd11b0fad3bac37b7\" class=\"member_mention\" data-editable=\"true\" data-title=\"@题叶\" data-tip=\"p$b$790dccce26904cdcd11b0fad3bac37b7\">@题叶</a><a data-hash=\"85de6407f2219137df29b4249b91cfd5\" href=\"//www.zhihu.com/people/85de6407f2219137df29b4249b91cfd5\" class=\"member_mention\" data-editable=\"true\" data-title=\"@郭达峰\" data-tip=\"p$b$85de6407f2219137df29b4249b91cfd5\">@郭达峰</a> 等等等等，很多思想和新东西只有从他们身上才能学到。我还推荐你多参加技术交流会，多认识一些可以一起学习的小伙伴，你们可以互相交流并且一起成长\n<br>\n<br><b>你需要具备很强的自学能力、对技术有热情并且不断跟进</b>。因为 JavaScript/前端的社区非常非常活跃，有太多的新东西需要你自己来发现与学习：比如 Universal JavaScript、Isomorphic JavaScript、前端测试、HTML5 页游、WebRTC、WebSocket、CSS 4、SVG、HTTP/2、ES 7、React Native、Babel、TypeScript、Electron 等等等等…\n<br>\n<br>\n<br>虽然一下扯得有点多，但这些确实就是你未来将会遇到的。你并不需要全部掌握它们，但是却多多益善；你也可以专精在某几个方面，这已经足以让你成为非常专业的前端工程师。\n<br>\n<br><b>所以，如果你自认为涵盖了上述要求的 40%，欢迎简历发 huangxuan@wepiao.com ，实习/全职皆可～</b>\n<br>\n<br>\n<br>咦，这个结尾怪怪的……\n"
  },
  {
    "path": "_posts/2015-12-15-ios9-safari-web.markdown",
    "content": "---\nlayout:     post\ntitle:      \"「译」iOS 9，为前端世界都带来了些什么？\"\nsubtitle:   \"iOS 9, Safari and the Web: 3D Touch, new Responsive Web Design, Native integration and HTML5 APIs\"\ndate:       2015-12-15\nauthor:     \"Hux\"\nheader-img: \"img/post-bg-ios9-web.jpg\"\ncatalog:    true\ntags:\n    - Web\n    - 译\n---\n\n2015 年 9 月，Apple 重磅发布了全新的 iPhone 6s/6s Plus、iPad Pro 与全新的操作系统 watchOS 2 与 tvOS 9（是的，这货居然是第 9 版），加上已经发布的 iOS 9，它们都为前端世界带来了哪些变化呢？作为一个 web 开发者，是时候站在我们的角度来说一说了！\n\n\n> **注！** 该译文存在大量英文术语，笔者将默认读者知晓 ES6、viewport、native app、webview 等常用前端术语，并不对这些已知术语进行汉语翻译\n> 对于新发布或较新的产品名称与技术术语，诸如 Apple Pen、Split View 等专有名词，笔者将在文中使用其英文名，但会尝试对部分名词进行汉语标注\n> 另外，出于对 wiki 式阅读的偏爱，笔者为您添加了很多额外的链接，方便您查阅文档或出处\n\n\n## 简而言之\n\n如果你不想阅读整篇文章，这里为你准备了一个总结：\n\n\n#### 新的设备特性\n\n* iPhone 6s 与 6s Plus 拥有 **“[3D Touch](http://www.apple.com/iphone-6s/3d-touch/)”**，这是一个全新的硬件特性，它可以侦测压力，是一个可以让你拿到手指压力数据的 API\n* iPad Pro 的 viewport 为 1024px，与以往的 iPad 全都不同\n* 想在 iPad Pro 上支持新的 Apple Pen？不好意思，目前似乎并没有适用于网站的 API \n\n#### 新的操作系统特性（与 web 相关的）\n\n* iPad 上的 Safari 现在可以通过 [Split View](https://developer.apple.com/library/prerelease/ios/documentation/WindowsViews/Conceptual/AdoptingMultitaskingOniPad/QuickStartForSlideOverAndSplitView.html#//apple_ref/doc/uid/TP40015145-CH13-SW1)（分屏视图）与其他应用一起使用，这意味着新的 viewport 尺寸将会越来越常见\n* 新的 Safari View Controller（[`SFSafariViewController`](https://developer.apple.com/library/prerelease/ios/documentation/SafariServices/Reference/SFSafariViewController_Ref/index.html#//apple_ref/occ/cl/SFSafariViewController)）可以让你在 native app 内提供与 Safari 界面、行为连贯一致的应用内网页浏览体验\n* 注意啦！Safari 新加入了 Content Blocker（内容拦截器）。以后，并不是所有的访问都一定会出现在你的 Google Analytics 了\n* [Universal Links](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/UniversalLinks.html#//apple_ref/doc/uid/TP40016308-CH12) 可以让应用的拥有者在 iOS 内部“占有”自己的域名。因此，访问 yourdomain.com 将会打开你的应用（类似 Android 的 Intents 机制）\n* [App Search（应用搜索）](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/index.html#//apple_ref/doc/uid/TP40016308)：现在，Apple 将会抓取你的网页内容（与 native app 内容）用于 Spotlight 与 Siri 的搜索结果，[想知道你的标签都兼容吗？](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/WebContent.html#//apple_ref/doc/uid/TP40016308-CH8)\n* 你的网站现在可以通过 JavaScript API 访问 iCloud 的用户数据\n\n#### 新的 API 支持\n\n* [Performance Timing API](https://developer.mozilla.org/en-US/docs/Web/API/Performance/timing) 在 iOS 9 得到回归\n* 关于 HTML5 Video，你现在可以在支持 [Picture in Picture（画中画）](https://developer.apple.com/library/prerelease/ios/documentation/WindowsViews/Conceptual/AdoptingMultitaskingOniPad/QuickStartForPictureInPicture.html#//apple_ref/doc/uid/TP40015145-CH14)的 iPad 设备上提供这项新功能；你的视频甚至可以在 Safari 关闭后继续播放\n* 更好的 ES6 支持：classes（类）, computed properties（可计算属性）, template literals（模版字符串）等\n* Backdrop CSS filters（背景滤镜）\n* CSS @supports 与 CSS Supports JavaScript API \n* CSS Level4 伪选择器\n* 用于支持分页内容的 CSS Scroll Snapping\n* WKWebView 现在可以访问本地文件了\n* 我们仍然需要等待 Push Notification，camera access，Service Workers 这些现代 web API 的到来\n\n#### 新的操作系统\n\n* 新一代 Apple TV 的 **tvOS**： 没有浏览器，也没有 webview。但是 JavaScript、XHR 和 DOM 可以通过一个叫做 TVML 的标记语言来使用\n* Apple Watch 的 **watchOS**：完全没有任何浏览器和 webview\n\n\n> **再注！** 由于原文写于 Apple 发布会之前，为了不让读者感到奇怪，笔者将会对文章进行适当改写与补充，以保证本文的连贯性\n\n\n## 新的 iOS 设备特性\n\n### iPhones 6s 与 3D Touch\n\n从 web 设计与开发的角度来说，新的 iPhone 6s 与 6s Plus 与之前的版本并没有太多差别。不过，有一个特性注定会吸引我们的目光：**3D Touch**\n\n我们无法确定 Apple 是不是只是重命名了一下 “Force Touch”（用于 Apple Watch、TrackPad 2 与最新的 MacBook 上）或者 3D Touch 的确是一个为 iPhone 定制的似曾相识却不同的东西。3D Touch 允许操作系统和应用侦测每一个手指与屏幕接触时的压力。从用户体验的角度来说，最大的变化莫过于当你用点力去触碰或者拖拽屏幕时，操作系统将会触发诸如 peek，pop 这些新机制。那么问题来了：**我们是否能够在网站中使用这个新玩意呢？让我们一点点来看：**\n\niOS 9 搭载的 Safari 包含了一些用于 “Force Touch” 的新 API，但它们其实并不是那个用于 iPhone 6s 3D Touch 的 API。你可以理解为这些 API 就是 MacBook 版 Safari 里为 Force Touch 准备的那些 API ，因为共享一套 codebase，所以它理所当然得存在了 iOS 版里而已。\n\nForce Touch API 为我们添加了两个新东西：\n\n1. 你的 click 事件处理函数将会从 MouseEvent 中收到一个新的属性：`webkitForce`\n2. DOM 也新增了四个事件：`(webkit)mouseforcewillbegin`，`mouseforcedown`，`mouseforceup` 与 `mouseforcechange`。下边的示意图将告诉你这些事件是在何时被触发的：\n\n![Force Events](http://www.mobilexweb.com/wp-content/uploads/2015/09/foceevents.png)\n\n\n相信你已经从它们的名字中意识到了，这些事件都是基于鼠标而非触摸的，毕竟它们是为 MacBook 设计的。并且，TouchEvent 也并没有包含 `webkitForce` 这个属性，它仅仅存在于 MouseEvent 里。在 iOS Safari 里，你确实可以找到 `onwebkitmouseforce` 这一系列事件处理器，但是很可惜它们并不会被触发，click 返回的 MouseEvent 也永远只能得到一个 `webkitForce: 0`\n\n\n可喜可贺的是，故事还没有结束。[Touch Events v2 draft spec（触摸事件第二版草案）](https://w3c.github.io/touch-events/) 中正式添加了 `force` 属性。3D Touch 也得以在 iPhone 6s 与 6s+ 中通过 TouchEvent 访问到。不过，笔者也要在这里提醒大家，由于没有 `webkitmouseforcechange` 这样给力的事件，在手机上我们只能通过 **轮询 TouchEvent 的做法** 来不断检测压力值的改变……非常坑爹\n\n[@Marcel Freinbichler](https://twitter.com/fr3ino) 第一个在 Twitter 上晒出了自己的 [Demo](http://freinbichler.me/apps/3dtouch)。在 6s 或 new Macbook 的 Safari（目前仅 Safari 支持）上访问就可以看到圆圈会随着压力放大。墙内的小伙伴可以直接试试下面这个圆圈，体验下 3D/Force Touch 带来的的奇妙体验。\n\n<iframe src=\"//huangxuan.me/forcify/\" style=\"\n    width:100%;\n    height:500px;\n    border: 0;\n\"></iframe>\n\n如果你不巧在用不支持 3D/Force Touch 的设备，发现尼玛用力按下去之后居然圆圈也有反映！？\n\n放心，这真的不是你的设备突然习得了“感应压力”这项技能，而是因为 [Forcify](http://huangxuan.me/forcify) 是一个用于在所有设备上 polyfill 3D/Force Touch API 的 JS 库……它不但封装了 OSX/iOS 两个平台之间 API 的差异，还使用\"长按\"来模拟了 `force` 值的变化……\n\n\n\n### iPad Pro\n\n全新的 iPad Pro（12.9 寸）打破了以往 iPad 渲染网站的方式。在此之前，市面上所有的 iPad（从初代 iPad，到 iPad Air 4，到 iPad Mini）都是以 768px 的宽度提供 viewport。\n\n而屏幕更大的 iPad Pro 选择了宽 1024px 的 viewport，这使得它天生就能容纳更多的内容。不少人说iPad Pro 就是抄 Microsft Surface Pro 的嘛……嗯哼，IE/Edge 在 Surface Pro 上就是以 1024px 作为视口宽度的……\n\n从交互的角度上来说，iPad Pro 虽然不支持 3D Touch，但是可以搭配 Smart Keyboard 与/或 Apple Pen（带有压力侦测）使用。对于键盘其实并没有什么好说的，如果一个网站在搭配键盘的桌面电脑上好用，它在 iPad Pro 上应该也不赖。而对于 Apple Pen，很可惜，目前似乎并没有 API 能让你在网站上获得这根笔的压力与角度。\n\n\n## 新的 iOS 操作系统特性\n\n### iPad 上的多任务处理\n\n自 iOS 9 起，iPad 允许两个应用在同一时刻并肩执行，有三种方式：**Slide Over**，**Split View** 与 **Picture-in-Picture**。不过，每一种方式都有其硬件需求，比如说 Slide Over 需要 iPad Air, iPad Mini 2 以上的设备，而 Split View 由于对内存的要求目前只支持 iPad Air 2 与 iPad Pro。\n\n#### Slide Over（滑过来！）\n\nSlide Over 支持的 App 并不多，不过 Safari 名列其中，这意味着我们的网站将可能在这个模式下被渲染。当网站处于 Slide Over 模式下时，它将在屏幕的右 1/4 位置渲染，并且置于其他 native app 之上。\n\n这个模式也为 Responsive Web Design（响应式网站设计）提出了新的挑战：**一个只为 iPad 优化的网站，也需要能在该设备上以无需手动刷新的形式支持小屏幕的渲染。**因此，如果你正在使用服务器端探测（RESS），那么你的 iPad 版本需要以某种方式包含手机版本的网站，或者在进入该模式后重新加载一次。（如果你不了解 RESS，你可以观看我的[另一篇博文](/2014/11/20/responsive-web-design/)）\n\n\n![Slide Over](http://www.mobilexweb.com/wp-content/uploads/2015/09/slideover.png)\n\n在这个模式下，无论横屏还是竖屏，所有的 iPad（包括 Pro）都会把你的网站以 320px 的 viewport 宽度进行渲染，就好像在一个大 iPhone 5 上一样。你可以在 CSS 中通过 media query（媒体查询）探测到这个模式：\n\n```css\n/* iPad Air or iPad Mini */\n(device-width: 768px) and (width: 320px)\n/* iPad Pro */\n(device-width: 1024px) and (width: 320px)\n```\n\n#### Split View（分屏视图）\n\n在较新版本的 iPad 上，你可以将 Slide Over 的 Side View（侧视图）升级为 Split View。此时，两个应用将以相同比例在你的屏幕上同时工作。\n\n在这个模式下，我们的网站将可能……\n\n* **以屏幕 1/3 比例渲染时**，viewport 在 iPad Air/mini 犹如 iPhone 5，宽 320px。而在 iPad Pro 上则像是 iPhone 6：宽 375px\n* **以屏幕 1/2 比例渲染时**，viewport 在 iPad Air/mini 上呈现为 507px 宽，而在 iPad Pro（横屏）下呈现为 678px 宽\n* **以屏幕 2/3 比例渲染时**，viewport 在 iPad Air/mini 上呈现为 694px 宽，而在 iPad Pro（横屏）下呈现为 981px 宽\n\n![Split View](http://www.mobilexweb.com/wp-content/uploads/2015/09/splitview.png)\n\n\n#### Picture in Picture（画中画）\n\n在一些较新版本的 iPad 上，使用 HTML5 video 标签的网站可以将其暴露到 Picture in Picture 机制中。通过 API（本文稍后会讲）或用户的触发，视频可以独立于网站在其他应用的上方继续播放。\n\n![Picture in Picture](http://www.mobilexweb.com/wp-content/uploads/2015/09/pip.png)\n\n\n### iOS 9 下的响应式网页设计\n\n下图向你展示了 iOS 9 所有可能的 viewport 尺寸，检查检查你的响应式断点都包含它们了吗？\n\n![iOS 9 RWD](http://www.mobilexweb.com/wp-content/uploads/2015/09/ios9rwd.png)\n\n### Safari View Controller \n\n如果你用过 Twitter 或者 Facebook（或者微信，微博……），那么你一定知道很多 native app 在打开一个网页链接时并不会默认使用 Safari。它们试图让你留在它们的应用里，所以通过提供 webview 让你在应用内进行网页浏览。可是问题在于，这类 webview 并不会与浏览器共享 cookies，sessions，autofill（自动填充）与 bookmark（书签），为了解决这些问题，就有了 Safari View Controller。\n\n现在，native app 可以使用 Safari View Controller 来打开网站，它提供与 Safari 完全一致的隐私政策、local storage，cookies、sessions 同时让用户留在你的 app 中，它通过一个 “Done”（完成）按钮使用户可以回到 native app 的上一个 controller。这个全新的 controller 还可以让我们在 Share（分享）按钮上添加自定义的操作，这些操作在用户使用 Safari 应用时并不会出现。同时，native app 对这个自定义 Safari 实例具有完全的内容控制，你可以屏蔽不想被渲染的内容。\n\n当你需要基于 web 的鉴权，比如 OAuth 时，使用 Safari View Controller 同样是一个好主意，这样就不再需要打开浏览器再重定向回你的应用。不过注意了，Safari View Controller 只适用于在线、公开的 web 内容。如果你的 web 内容假设在本地或者私服，那么 WKWebView 仍然是最推荐的选择。\n\n> 笔者八卦一下，Safari View Controller 实际上也算是半个社区推进的产物。早在 2014 年 12 月，Tumblr 的 iOS 工程师 Bryan 就发表了一篇著名的 [We need a “Safari view controller”](http://bryan.io/post/104845880796/we-need-a-safari-view-controller) 叙述现有 webview 在第三方登录鉴权时的窘境。\n> 2015 年 6 月，Apple Safari 工程师 Ricky Mondello 的 Twitter 宣告了这个设想的落地：You all asked for it. Come see me introduce it. Introducing Safari View Controller 1:30 PM, Tuesday. Nob Hill.\n\n\n### Safari Content Blockers\n\n现在，iOS 9 上的 Safari 支持一种全新的 App Extensions（应用拓展）：**Content Blocker**（内容拦截器）。这类拓展以 native app 的形式存在，你可以在 App Store 上下载到，它们可以拦截 Safari 内的任何内容，包括：跟踪器、广告、自定义字体、大图片、JavaScript 文件等等。\n\n作为 web 开发者，尽管我们不能禁用 Content Blocker，我们仍然应该注意到它们的存在。诸如 Crystal 的一些拦截器宣称他们[可以提高网页的打开速度](http://murphyapps.co/blog/2015/8/22/crystal-benchmarks)。Crystal 声称可以加快网页的加载速度 3.9 倍并且少用 53% 的带宽。不过问题是：到底哪些东西被拦截器拦截了？[这篇文章](http://thenextweb.com/apple/2015/08/27/content-blocking-in-ios-9-is-going-to-screw-up-way-more-than-just-ads/)提到了一些我们未来可能会遇到的问题。\n\n![crystal](http://www.mobilexweb.com/wp-content/uploads/2015/09/crystal.png)\n\n在 iOS 9 发布后，Peace，一个 Content Blocker，曾在 App Store 排名跻身前十。从用户的角度来说，如果一个网站由于被 Content Blocker 拦截了某些重要资源而不能正常工作，你可以长按重新加载按钮并且以不启用 Content Blocker 的方式重新加载这个网站（见下图，来自 MacWorld.com）\n\n![disable content blocker](http://www.mobilexweb.com/wp-content/uploads/2015/09/macworld.png)\n\nContent Blocker 能隐藏元素，也有能力通过 CSS 选择器、域名、类型、或者 URL 来过滤并拦截某个文件的加载，[Purify Blocker](https://itunes.apple.com/us/app/purify-blocker-fast-clutter/id1030156203?ls=1&mt=8) 给用户提供了拦截某一种内容类型的进阶选项，比如 Web Fonts。\n\n![purify](http://www.mobilexweb.com/wp-content/uploads/2015/09/purify.png)\n\n### WKWebView 的增强\n\nUIWebView 已经被官方弃用，虽然它还在在那，不过它再也不会得到什么升级。与此相反，WKWebView 正在取代它的位置。一个最受期待的特性现在终于推出：加载本地文件到 WKWebView。因此，现在 Apache Cordova 应用与其他 web 内容都可以直接从 iOS 包中使用本地文件了，不再需要各种诡异的 hack 了。\n\n此外，还有一些新特性也一并推出。比如说，通过 WKWebsiteDataStore，Objective-C 或 Swift 有能力查询与管理 webview 的本地存储（比如 localStorage 或 IndexedDB）。这就允许我们将原有的数据存储替换成新的某些东西，比如说替换成一个不永久的（Chrome for iOS 的隐身模式就需要这种东西）\n\n### Universal Links（通用链接）\n\n如果你既有一个网站，又有一个 native app，你现在可以通过 Universal Links 来增强用户体验。它允许你在操作系统内“占有”自己的域名，这样，一切指向你网站的链接都会被重定向到你的 app。\n\n目前，所有的 app 都是通过自定义 URI 来达到这个效果的，比如 `comgooglemaps://` 就可以用来从网站或者其他原生 iOS 应用中打开 Google Maps。\n\n想要提供这个特性的话，你首先需要在 native app 中实现 Deep Linking（深度链接），让应用中的内容与 Safari 的 URL 吻合。然后，你需要在 Apple 的网站上关联你的域名，取得这个域名的 SSL 认证并且把签名后的 JSON 部署到该域名上。这是为了防止第三方的应用“占据”了属于你而不属于他们的域名，比如说 twitter.com 被非 Twitter 的其他应用占据掉。\n\n目前唯一的缺点是用户好像并不能决定到底以哪种方式来打开内容（使用 web 还是 app），不过我们可以观望一段时间看看它会如何发展。在不远的这段时间里，你可能会发现在网站或 Google 搜索里点击一个链接时会没有任何预警的就跳进了 native app 里。\n\n### App Search（应用搜索）\n\nApple 带着自己的 web 蜘蛛杀进了搜索的市场，而我们需要支持它得以在 Siri 与 Spotlight 中提升自己的曝光率。这在我们同时拥有网站与 app 时尤为重要，因为现在 Apple 会索引你网站的内容，但打开时却可能将用户带到了 app 里去。\n\n尽管这会开启 SEO 的新篇章，不过却相当容易。你需要使用一些标签标准，诸如 [Web Schema](http://schema.org/)、[AppLinks](http://applinks.org)、[OpenGraph](http://ogp.me) 或者 [Twitter Cards](https://dev.twitter.com/cards/mobile)，配合上 App Banner 与 `app-argument`，如果你有你自己的 native app 的话。\n\n> 关于“让你的网页支持 Apple 搜索”的更多详情，请查阅 Apple 官方文档 [Mark Up Web Content](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/WebContent.html#//apple_ref/doc/uid/TP40016308-CH8-SW5)\n\nApple 刚刚发布了一个 [App Search Validation Tool（应用搜索验证工具）](https://search.developer.apple.com/appsearch-validation-tool/)来帮助你搞清楚，需要向你的网站添加什么才能支持 App Search\n\n![App Search](http://www.mobilexweb.com/wp-content/uploads/2015/09/appsearch-1024x467.png)\n\n### CloudKit JS\n\n如果你拥有一个 native app，你很可能会将用户数据保存在 iCloud 上。在过去，只有 iOS 与 Mac 应用被允许使用它。现在，通过 CloudKit JS，你的网站也可以连接上 iCloud 数据了。\n\n### Back Button\n\n现在，当你链接到一个 native app 时（通过自定义 URI 或者 Universal Link），Safari 会询问用户是否想要使用 native app 打开这个链接（见下图）。如果用户同意了，这个应用将被打开，并且在左上角会有一个返回按钮可以返回 Safari ，返回到你的网站。\n\n<img src=\"http://www.mobilexweb.com/wp-content/uploads/2015/09/back.png\" alt=\"backbutton\" width=\"320\" />\n\n## 新的 API 支持\n\n### Navigation Timing API\n\nNavigation Timing API 在 iOS 9 迎来了回归。让我们回忆一下，这货添加于 8.0 却在一周后的 8.1 中去掉了。这对于 Web 性能是个好消息。通过这个 API，我们可以更精确的测量时间，还可以获得一系列有关加载过程的时间戳，它们对于追踪与在真实场景中做决策来改进用户体验都非常有用。\n\n\n### Picture in Picture\n\nPiP API（被称为 Presentation Mode API）目前只支持 iOS，它允许我们手动让一个 `<video>` 元素进入或离开 PiP 模式如果 `video.webkitSupportsPresentationMode` 是支持的。\n\n举个例子，我们可以在内嵌模式与 PiP 模式中切换：\n\n```js\nvideo.webkitSetPresentationMode(\n    video.webkitPresentationMode === \"picture-in-picture\" ?\n    \"inline\" : \n    \"picture-in-picture\"\n);\n```\n\n我们还可以通过新的 `onwebkitpresentationmodechanged` 事件来检测 Presentation Mode（展示模式）的变化。\n\n\n### Backdrop CSS\n\niOS 7 与最近的 Mac OS 使用 Backdrop filter（背景滤镜）来模糊背景（指 native 开发），而在网站上实现这个却并不容易。\n\niOS 9 上的 Safari 现在支持了来自 Filter Effect v2 spec（滤镜特效第二版规范）的 **backdrop-filter**。比如说，我们可以使用一个半透明的背景并且对其背后的背景使用滤镜：\n\n```css\nheader {\n   background-color: rgba(255, 255, 255, 0.4);\n   -webkit-backdrop-filter: blur(5px);\n   backdrop-filter: blur(5px);\n}\n```\n\n![backdrop](http://www.mobilexweb.com/wp-content/uploads/2015/09/backdrop.png)\n\n\n### CSS Scroll Snapping\n\n在 web 上实现分页内容（比如相册跑马灯）总是非常麻烦，无论是使用 JavaScript 框架、touch 事件还是 hacking 滚动条等等。Apple 新添加了一个很赞的 CSS 特性叫做 CSS Scroll Snapping。这个特性新增了一系列的 CSS 属性让你定义规则或者不规则的 snap zone（停留区域），这样滚动的位置就会“啪”地一下停在这个区域，而非像以前一样可以停在任何地方。  \n\n来看个例子：\n\n```css\n#photo-gallery{\n    width: 100%;\n    overflow-x: scroll;\n    -webkit-scroll-snap-points-x: repeat(100%);\n    -webkit-scroll-snap-type: mandatory;\n}\n```\n\n> 想要看个跑起来后的例子？笔者为大家准备了 webkit 的官方 [demo](http://www.webkit.org/demos/scroll-snap/)，不过这个属性目前只支持 iOS 9 Safari 哦，并不支持 webview\n\n\n### CSS Supports\n\nCSS Supports，包括 CSS `@supports` 与来自 CSS Conditional Rules Module Level 3 spec 的 JavaScript CSS Supports API 都在 iOS 上迎来降临。现在，我们可以针对某个 CSS 属性的特定值的支持情况来编写代码：\n\n```css\n@supports(-webkit-scroll-snap-type: mandatory) {\n    /* we use it */\n}\n```\n\n同样，使用 JavaScript：\n\n```js\nif (CSS.supports(\"-webkit-scroll-snap-type\", \"mandatory\")) {}\n```\n\n### 一些细微的改进\n\n* ECMAScript 6 的更完善支持：classed、computed properties、template literial 与 week sets\n* 新的 CSS Level4 伪类/元素选择器：`:not`、`:matches`、`:any-link`、`:placeholder-shown`、`:read-write`、`:read-only`\n* Native app 现在可以通过 extension 来向 Safari 的 Shared Links（分享链接）窗口上注入信息\n* 大量无前缀 CSS 属性的支持（终于），比如 transition、animation、@keyframes、flex 与 columns\n* Mac OS El Capitán 上的 Safari 9 提供了一个全新设计的 Web Inspector（Web 检查器）。幸运的是，iOS 9 的远程调试完全兼容 Mac OS 上的 Safari 8，所以你倒是不用急着升级你的 Mac OS\n* iOS 9 通过 `-apple-font` 加入了一些 Dynamic Fonts（动态字体），并且它们现在应用的是 Apple 的新字体：San Francisco，笔者的博客就已经用上它啦\n* scrollingElement 现在可用了\n* `<input type=file>` 现在允许你从 iCloud Drive 与已安装的第三方应用，比如 Google Drive 中选择文件\n\n<img src=\"http://www.mobilexweb.com/wp-content/uploads/2015/09/IMG_2017.png\" alt=\"input file\" width=\"320\" />\n\n* 当你加载一个 HTTPS 协议的页面时，你不能混用 HTTP 与 HTTPS 的资源\n\n\n## Bugs\n\nBug 通常都要在几周之后才会显露出来，我也会持续跟进并更新这篇文章。目前为止，我的发现如下：\n\n* 对于 Home Screen webapps（添加至主屏的 web 应用），`apple-mobile-web-app-status-bar-style` 这个 meta 标签不起作用了！所以你现在不能再像过去一样使用 `black-translucent` 让你的 webapp 渲染在状态栏的后面了。（iOS 9.2 fixed 了这个 bug）\n* Speech Synthesis API （语音综合 API）不再工作了\n\n\n## 仍在等待……\n\n当 Mac 上的 Safari、桌面电脑与 Android 上的 Chrome 都已经为网站支持 Push Notification （通知推送）时，iOS 上的 Safari 仍然不支持这个特性。就 API 而言，我们仍然没有：WebRTC、getUserMedia、Service Worker、FileSystem API、Network Information API、Battery Status API、Vibration API 等等……你又在 iOS 上等待哪些特性呢？ \n\n## watchOS 与 tvOS\n\n新发布的 watchOS 2.0 与 tvOS 9.0 都是基于 iOS 的操作系统，它们针对特定的设备发行（Apple Watch 与新的 Apple TV）。从用户的角度来说，那里并没有浏览器了。从开发者的角度，那里也没有 Webview 了。\n\n尽管有不少人抱怨（大部分都是针对 webview 的缺失），我并不能确定这是不是个坏主意。我猜测 Apple 会尝试通过 Siri 来将 “web” 带给 TV、手表、甚至 CarPlay 的用户。所以，如果你遵循了上述的 “App Search” 的步骤，你的内容将可能通过 Siri 在这些设备上以 widget（小部件）或者快捷回复的形式变得可以访问。\n\n对于 Apple TV ，它支持使用 JavaScript、DOM API 与 XMLHttpRequest 来让我们构建某种类似 Client-Server webapp 的东西。没有 HTML 和 CSS，这是什么把戏？其实它支持的叫 TVML，是一种基于 XML、为那些可以被渲染在 TV 屏幕上的特定内容而优化后的标签。这些标签只可以在来自应用商店的 native app 中渲染，但是这些 TVML 是由服务器端来生成的。\n\n\n## 著作权声明\n\n本文译自 [iOS 9, Safari and the Web: 3D Touch, new Responsive Web Design, Native integration and HTML5 APIs --- Breaking the Mobile Web](http://www.mobilexweb.com/blog/ios9-safari-for-web-developers)   \n译者 [黄玄](http://weibo.com/huxpro)，首次发布于 [Hux Blog](http://huangxuan.me)，转载请保留以上链接"
  },
  {
    "path": "_posts/2015-12-28-css-sucks-2015.markdown",
    "content": "---\nlayout:     keynote\ntitle:      \"都 2015 年了，CSS 怎么还是这么糟糕\"\nsubtitle:   \"🎞  Slides:CSS Still Sucks 2015\"\niframe:     \"//huangxuan.me/css-sucks-2015/\"\ndate:       2015-12-28\nauthor:     \"Hux\"\ntags:\n    - Web\n    - CSS\n---\n\n\n> 下滑这里查看更多内容\n\n\n### [Watching Fullscreen →](https://huangxuan.me/css-sucks-2015/)\n\n<div class=\"visible-md visible-lg\">\n<img src=\"//huangxuan.me/css-sucks-2015/attach/qrcode.png\" width=\"350\"/>\n<small class=\"img-hint\">你也可以通过扫描二维码在手机上观看</small>\n</div>\n\n\n这个 Web Slides 开源在[我的 Github 上](https://github.com/Huxpro/css-sucks-2015)，欢迎你帮助我完善这个展示文稿，你可以给我提 issue，可以 fork & pull request。如果它能帮助到你了，希望你还能不吝啬 star 一下这个项目\n\n\n### Catalog\n\n- Document Times\n    - Frameworks\n    - Style Guide\n        - **OOCSS**\n        - **SMACSS**\n    - **Pre-processer**\n    - **PostCSS**\n- Application Times\n    - **Shadow DOM**\n    - **CSS \"4\"**\n    - Naming Convention\n        - **BEM**\n        - **SUIT**\n    - **Atomic CSS**\n    - **CSS in JS**\n    - **CSS Modules**  \n        - Interoperable CSS\n    - PostCSS, again\n- My Opinionated Proposal\n    - **POCss**\n\n## POCss: Page Override Components CSS\n\n### 1. Scoping Components <br><small style=\"line-height:2em;\">*CSS Blocks should only be used inside a component of the same name.*</small>\n\n```scss\n// Component/index.scss\n.ComponentName {\n    &--mofierName {}\n    &__decendentName {\n        &--modifierName {}\n    }\n    .isStateOfComponent {}\n}\n```\n\n```javascript\n// Component/index.js\nrequire('./index.scss');\n```\n\nCSS is *always bundled* with components<br>(from loading, mount to unmount)\n\n### 2. Components can be Overrode by Pages <br><small style=\"line-height:2em;\">*There is always requirements to rewrite styles of components in pages*</small>\n\n```scss\n// Pages/PageA.scss\n#PageA {\n    .pagelet-name {\n        .pagelet-descendent-name {}\n    }\n    .ComponentName{ /* override */ }\n}\n```\n\n```javascript\n// Pages/index.js\nrequire('./PageA.scss');\n```\n\n- *#Page* for absolutely scoping between pages\n- *.pagelet-name* should be lowercase to prevent conflicting with components\n\n### Why POC?\n\n- **It's technology-agnostic**\n<small>\n    *One css framework can be played with whatever technology stacks*<br>\n    *You can combined Scss, PostCSS and whatever you want*\n</small>\n\n- **Solving problems, and easy**\n<small>\n    *Makes reading and teamwork much easier*<br>\n    *Get all benefit from BEM, SUITCSS and others*\n</small>\n\n- **Leverage the power of cascading properly**\n<small>\n    *Scoping components but allow reasonable overriding*<br>\n    *It's pragmatic, flexible and hitting the sweet spot*\n</small>\n\n### Thanks\n\n[Reveal.js](http://lab.hakim.se/reveal-js)\n"
  },
  {
    "path": "_posts/2016-02-01-React-vs-Angular2.markdown",
    "content": "---\nlayout:     post\ntitle:      \"「译」React vs Angular 2：冰与火之歌\"\nsubtitle:   \"React versus Angular 2: There Will Be Blood\"\ndate:       2016-02-01 12:00:00\nauthor:     \"Hux\"\nheader-img: \"img/post-bg-re-vs-ng2.jpg\"\nheader-mask: 0.3\ncatalog:    true\ntags:\n  - Web\n  - JavaScript\n  - 译\n---\n\n> 这篇文章转载自[我在知乎专栏「前端外刊评论」上发表的文章](http://zhuanlan.zhihu.com/FrontendMagazine/20549104)。\n\n\n[Angular 2](https://angular.io/) 已经发布 Beta 版，而且似乎很有信心在 2016 年成为热门框架。是时候进行一场巅峰对决了，我们来看看它如何与 [React](https://facebook.github.io/react/) 这个 2015 年的新宠抗衡。\n\n\n**免责声明：**我之前很喜欢使用 Angular 1，不过在 2015 年转到了 React。最近我也在 Pluralsight 上发布了一门关于 [React 和 Flux 的课程](https://www.pluralsight.com/courses/react-flux-building-applications)（[免费试学](http://app.pluralsight.com/signup)）。所以，**是的，我本人是有偏见的，但我不会偏袒任何一方。**\n\n好了，我们开始吧，这场对决将会非常血腥。\n\n![](https://cdn-images-1.medium.com/max/800/1*MRPl_SNuRGJchb6eOAnkSA.jpeg)\n\n图片来源：[@jwcarrol](https://twitter.com/jwcarroll)\n\n## 两者根本不具有可比性！\n\n是的是的，Angular 是框架，React 是类库。所以有人觉得比较这两者没有逻辑性可言。大错特错！\n\n> 选择 Angular 还是 React 就像选择直接购买成品电脑还是买零件自己组装一样。\n\n两者的优缺点本文都会提及，我会拿 React 语法和组件模型跟 Angular 的语法和组件模型做对比。这就像是拿成品电脑的 CPU 跟零售的 CPU 做对比，没有任何不妥。\n\n## Angular 2 的优点\n\n我们先看 Angular 相对 React 有哪些优势。\n\n#### **无选择性疲劳**\n\nAngular 是一个完整的框架，本身就提供了比 React 多得多的建议和功能。而要用 React，开发者通常还需要借助别的类库来打造一个真正的应用。比如你可能需要额外的库来处理路由、强制单向数据流、进行 API 调用、做测试以及管理依赖等等。要做的选择和决定太多了，让人很有压力。这也是为什么 React 有那么多的入门套件的原因（我自己就写了两个：[1](https://github.com/coryhouse/react-flux-starter-kit)、[2](https://github.com/coryhouse/react-slingshot)）。\n\nAngular 自带了不少主张，所以能够帮助你更快开始，不至于因为要做很多决定而无所适从。这种强制的一致性也能帮助新人更快适应其开发模式，并使得开发者在不同团队间切换更具可行性。\n\nAngular 核心团队让我非常欣赏的一点是，他们拥抱了 TypeScript，这就造成了另一个优势。\n\n#### TypeScript = 阳关大道\n\n没错，并非所有人都喜欢 TypeScript，但是 Angular 2 毅然决然地选择了它确实是个巨大的优势。反观 React，网上的各种示例应用令人沮丧地不一致——ES5 和 ES6 的项目基本上各占一半，而且目前存在[三种不同的组件声明方式](http://jamesknelson.com/should-i-use-react-createclass-es6-classes-or-stateless-functional-components/)。这无疑给初学者造成了困惑。（Angular 还拥抱了装饰器（decorator）而不是继承（extends）——很多人认为这也是个加分项）。\n\n尽管 Angular 2 并不强制使用 TypeScript，但显然的是，Angular 的核心团队默认在文档中使用 TypeScript。这意味着相关的示例应用和开源项目更有可能保持一致性。Angular 已经提供了[非常清晰的关于如何使用 TypeScript 编译器的例子](https://angular.io/docs/ts/latest/quickstart.html)。（诚然，目前[并非所有人都在拥抱 TypeScript](http://angularjs.blogspot.com/2015/09/angular-2-survey-results.html)，但我有理由相信等到正式发布之后，TypeScript 会成为事实上的标准）。这种一致性应该会帮助初学者避免在学习 React 时遇到的疑惑和选择困难。\n\n#### 极少的代码变动\n\n2015 年是 [JavaScript 疲劳](https://medium.com/@ericclemmons/javascript-fatigue-48d4011b6fc4#.559iqxb39)元年，React 可以说是罪魁祸首。而且 React 尚未发布 1.0，所以未来还可能有很多变数。React 生态圈依旧在快速地变动着，尤其是[各种 Flux 变种](https://github.com/kriasoft/react-starter-kit/issues/22)和[路由](https://github.com/rackt/react-router)。也就是说，你今天用 React 写的所有东西，都有可能在 React 1.0 正式发布后过时，或者必须进行大量的改动。\n\n相反，Angular 2 是一个对已经成熟完整框架（Angular 1）的重新发明，而且经过仔细、系统的设计。所以 Angular 不大可能在正式发布后要求已有项目进行痛苦的代码变动。Angular 作为一个完整的框架，你在选择它的时候，也会信任其开发团队，相信他们会认真决定框架的未来。而使用 React，一切都需要你自己负责，你要自己整合一大堆开源类库来打造一个完整的应用，类库之间互不相干且变动频繁。这是一个令人沮丧的耗时工作，而且永远没有尽头。\n\n#### **广泛的工具支持**\n\n后面我会说，我认为 React 的 JSX 是非常耀眼的亮点。然而要使用 JSX，你需要选择支持它的工具。尽管 React 已经足够流行，工具支持不再是什么问题，但诸如 IDE 和 lint 工具等新工具还不大可能很快得到支持。Angular 2 的模版是保存在一个字符串或独立的 HTML 文件中的，所以不要求特殊的工具支持（不过似乎 Angular 字符串模版的智能解析工具已经呼之欲出了）。\n\n#### Web Components 友好\n\nAngular 2 还拥抱了 Web Component 标准。唉，真尴尬我居然一开始忘记提到这点了——最近我还发布了一门关于[Web Components 课程](https://www.pluralsight.com/courses/web-components-shadow-dom)呢！简单来说，把 Angular 2 组件转换成原生 Web Components 应该会比 React 组件容易得多。固然 Web Components 的[浏览器支持度依然很弱](http://jonrimmer.github.io/are-we-componentized-yet/)，但长期来看，对 Web Components 友好是很大的优势。\n\nAngular 的实现有其自身的局限和陷阱，这正好让我过渡到对 React 优势的讨论。\n\n### React 的优点\n\n现在，让我们看看是什么让 React 如此与众不同。\n\n#### **JSX**\n\nJSX 是一种类似 HTML 的语法，但它实际上会被编译成 JavaScript。将标签与代码混写在同一个文件中意味着输入一个组件的函数或者变量时你将享受到自动补全的福利。而 Angular 基于字符串的模版就相形见绌了：很多编辑器都不会高亮它们（只会显示单色）、只有有限的代码补全支持，并且一直到运行时才会报错。并且，通常你也只能得到很有限的错误提示。不过，Angular 的团队[造了一个自己的 HTML 解析器来解决这个问题](https://github.com/angular/angular/issues/4417)。（叼叼叼！）\n\n如果你不喜欢 Angular 的字符串模版，你可以把模版移到一个单独的文件里去。不过这样你就回到了我认为的“老样子”：你需要在自己脑袋里记住这两个文件的关联，不但没有代码自动补全，也没有任何编译时检查来协助你。这听起来可能并不算什么……除非你已经爱上了与 React 相伴的日子。在同一个文件中组合组件还能享受编译时的检查，大概是 JSX 最与众不同的地方之一了。\n\n\n![](http://p5.qhimg.com/d/inn/8a99f370/2.jpg)\n\n对比 Angular 2 与 React 在标签忘记闭合时是如何表现的。\n\n关于为什么 JSX 是一个巨大的优势，可以看看 [JSX：硬币的另一面（JSX: The Other Side of the Coin）](https://medium.com/@housecor/react-s-jsx-the-other-side-of-the-coin-2ace7ab62b98#.5007n49wq). （P.S. 这是作者写的另一篇文章，如果大家希望我们可以把这篇也翻了，欢迎在评论区举手）\n\n#### React 报错清晰快速\n\n当你在 React 的 JSX 中不小心手抖打错时，它并不会被编译。这是一件非常美妙的事情：无论你是忘记闭合了标签还是引用了一个不存在的属性（property），你都可以立刻知道到底是哪一行出错了。**JSX 编译器会指出你手抖的具体行号**，彻彻底底加速你的开发。\n\n相反，当你在 Angular 2 中不小心敲错了一个变量时，鸦雀无声。**Angular 2 并不会在编译时做什么，它会等到运行时才静默报错。**它报错得*如此之慢*，我加载完整个应用然后奇怪为什么我的数据没有显示出来呢？这太不爽了。\n\n#### React 以 JavaScript 为中心\n\n终于来了。这才是 React 和 Angular 的根本区别。**很不幸，Angular 2 仍然是以 HTML 而非 JavaScript 为中心的。**Angular 2 并没有解决它设计上的根本问题：\n\n> Angular 2 继续把 “JS” 放到 HTML 里。React 则把 “HTML” 放到 JS 里。\n\n这种分歧带来的影响真是再怎么强调也不为过。它们从根本上影响着开发体验。Angular 以 HTML 为中心的设计留下了巨大的缺陷。正如我在 [JSX：硬币的另一面](https://medium.com/@housecor/react-s-jsx-the-other-side-of-the-coin-2ace7ab62b98#.jqh5kkxlk) 中所说的，JavaScript 远比 HTML 要强大。因此，**增强 JavaScript 让其支持标签要比增强 HTML 让其支持逻辑要合理得多**。无论如何，HTML 与 JavaScript 都需要某种方式以粘合在一起。React 以 JavaScript 为中心的思路从根本上优于 Angular、Ember、Knockout 这些以 HTML 为中心的思路。\n\n让我们来看看为什么。\n\n#### React 以 JavaScript 为中心的设计 = 简约\n\nAngular 2 延续了 Angular 1 试图让 HTML 更加强大的老路子。所以即使是像循环或者条件判断这样的简单任务你也不得不使用 Angular 2 的独特语法来完成。例如，Angular 2 通过两种语法同时提供了单向数据绑定与双向数据绑定，可不幸的是它们实在差得有点多：\n\n{% raw %}\n\n```hbs\n{{myVar}}        //单向数据绑定\nngModel=\"myVar\"  //双向数据绑定\n```\n\n{% endraw %}\n\n在 React 中，数据绑定语法不取决于数据流的单双向（数据绑定的单双向是在其他地方处理的，不得不说我觉得理应如此）。不管是单向还是双向数据流，绑定语法都是这样的：\n\n```js\n{myVar}\n```\n\nAngular 2 的内联母版（inline master templates）使用了这样的语法：\n\n{% raw %}\n```hbs\n<ul>\n  <li *ngFor=\"#hero of heroes\">\n    {{hero.name}}\n  </li>\n</ul>\n```\n{% endraw %}\n\n上面这个代码片段遍历了一组 hero，而我比较关心的几点是：\n\n- 通过星号来声明一个“母版”实在是太晦涩了\n- `hero` 前的英镑符号（`#`）用于声明一个局部模版变量。这个概念感觉非常鸡肋（如果你偏好不使用 `#`，你也可以使用 `var-` 前缀写法）\n-  为 HTML 加入了循环语义的HTML 特性（attribute）`ngFor` 是 Angular 特有的东西\n\n相比上面 Angular 2 的语法，React 的语法可是纯净的 JavaScript （不过我得承认下面的属性 `key` 是个 React 的私货）\n\n```hbs\n<ul>\n  { heroes.map(hero =>\n    <li key={hero.id}>{hero.name}</li>\n  )}\n</ul>\n```\n\n鉴于 JS 原生支持循环，React JSX 利用 JS 的力量来做到这类事情简直易如反掌，配合 `map`、`filter` 能做的还远不止此。\n\n去看看 [Angular 2 速查表](https://angular.io/docs/ts/latest/guide/cheatsheet.html)？那不是 HTML，也不是 JavaScript……这叫 **Angular**。\n\n> **读懂 Angular：** 学一大堆 Angular 特有的语法\n>\n> 读懂 React： 学 JavaScript\n\nReact 因为语法和概念的简约而与众不同。我们不妨品味下当今流行的 JS 框架/库都是如何实现遍历的：\n\n{% raw %}\n```\nEmber     : {{# each}}\nAngular 1 : ng-repeat\nAngular 2 : ngFor\nKnockout  : data-bind=\"foreach\"\nReact     : 直接用 JS 就好啦 :)\n```\n{% endraw %}\n\n除了 React，所有其它框架都用自己的专有语法重新发明了一个我们在 JavaScript 常见得不能再常见的东西：**循环**。这大概就是 React 的美妙之处，利用 JavaScript 的力量来处理标签，而不是什么奇怪的新语法。\n\nAngular 2 中的奇怪语法还有点击事件的绑定：\n\n```javascript\n(click)=\"onSelect(hero)\"\n```\n\n相反，React 再一次使用了普通的 JavaScript：\n\n```javascript\nonClick={this.onSelect.bind(this, hero)}\n```\n\n并且，鉴于 React 内建了一个模拟的事件机制（Angular 2 也有），你并不需要去担心使用内联语法声明事件处理器所暗含的性能问题。\n\n为什么要强迫自己满脑子都是一个框架的特殊语法呢？为什么不直接拥抱 JS 的力量？\n\n#### 奢华的开发体验\n\nJSX 具备的代码自动补全、编译时检查与丰富的错误提示已经创造了非常棒的开发体验，既为我们减少了输入，与节约了时间。而配合上热替换（hot reloading）与时间旅行（time travel），你将获得前所未有的开发体验，效率高到飞起。\n\n原文这里链了个 Youtube 上的视频：[Dan Abramov - Live React: Hot Reloading with Time Travel at react-europe 2015](https://www.youtube.com/watch?v=xsSnOQynTHs&feature=youtu.be)，大家自备梯子。\n\n#### 担心框架的大小？\n\n这里是一些常见框架/库压缩后的大小（[来源](https://gist.github.com/Restuta/cda69e50a853aa64912d)）：\n\n- **Angular 2:** 566k (766k with RxJS)\n- **Ember:** 435k\n- [**Angular 1**](https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js)**:** 143k\n- **React + Redux:** 139k\n\n列出的都是框架级的、用于浏览器且压缩后的大小（但并未 gzip）。需要补充的是，Angular 2 的尺寸在最终版本发布时应该会有所减小。\n\n为了做一个更真实的对比，我将 Angular 2 [官方教程](https://angular.io/docs/ts/latest/tutorial/)中的 Tour of Heroes 应用用 Angular 2 和 React（还用上了新的 [React Slingshot](https://github.com/coryhouse/react-slingshot) 入门套件）都实现了一遍，结果如何呢？\n\n\n- [**Angular 2**](https://github.com/coryhouse/angular-2-tour-of-heroes/tree/master)**:** 764k 压缩后\n- [**React + Redux**](https://github.com/coryhouse/react-tour-of-heroes)**:** 151k 压缩后\n\n可以看到，**做一个差不多的东西，Angular 2 目前的尺寸是 React + Redux 的五倍还多**。重要的事情再说一遍，Angular 2 的最终版本应该会减重。\n\n不过，我承认关于框架大小的担忧可能被夸大了：\n\n> 大型应用往往至少有几百 KB 的代码，经常还更多，不管它们是不是使用了框架。开发者需要做很多的抽象来构建一个复杂的软件。无论这些抽象是来自框架的还是自己手写的，它都会对应用的加载性能造成负面影响。  \n>\n>  就算你完全杜绝框架的使用，许多应用仍然是几百 KB 的 JavaScript 在那。 — Tom Dale [JavaScript Frameworks and Mobile Performance](http://tomdale.net/2015/11/javascript-frameworks-and-mobile-performance/)\n\nTom 的观点是对的。像 Angular、Ember 这样的框架之所以更大是因为它们自带了更多的功能。\n\n但是，我关心的点在于：很多应用其实用不到这种大型框架提供的所有功能。在这个越来越拥抱微服务、微应用、[单一职责模块（single-responsibility packages）](http://www.npmjs.com)的时代，**React 通过让你自己挑选必要模块，让你的应用大小真正做到量身定做**。在这个有着 200,000 个 npm 模块的世界里，这点非常强大。\n\n#### React 信奉[Unix 哲学](https://en.wikipedia.org/wiki/Unix_philosophy).\n\nReact 是一个类库。它的哲学与 Angular、Ember 这些大而全的框架恰恰相反。你可以根据场景挑选各种时髦的类库，搭配出你的最佳组合。JavaScript 世界在飞速发展，React 允许你不断用更好的类库去迭代你应用中的每个小部分，而不是傻等着你选择的框架自己升级。\n\nUnix 久经沙场屹立不倒，原因就是：\n\n> 小而美、可组合、目的单一，这种哲学永远不会过时。\n\nReact 作为一个专注、可组合并且目的单一的工具，已经被[全世界的各大网站们](https://github.com/facebook/react/wiki/Sites-Using-React)使用，预示着它的前途光明（当然，Angular 也被用于[许多大牌网站](https://www.madewithangular.com/#/)）。\n\n#### 谢幕之战\n\nAngular 2 相比第一代有着长足的进步。新的组件模型比第一代的指令（directives）易学许多；新增了对于同构／服务器端渲染的支持；使用虚拟 DOM 提供了 3-10 倍的性能提升。这些改进使得 Angular 2 与 React 旗鼓相当。不可否认，它功能齐全、观点鲜明，能够显著减少 “JavaScript 疲劳” 。\n\n不过，Angular 2 的大小和语法都让我望而却步。Angular 致力的 HTML 中心设计比 React 的 JavaScript 中心模型要复杂太多。在 React 中，你并不需要学习 `ng-什么什么` 这种框架特有的 HTML 补丁（shim），你只要写 JavaScript 就好了。这才是我相信的未来。\n\n### 著作权声明\n\n本文译自 [Angular 2 versus React: There Will Be Blood](https://medium.freecodecamp.com/angular-2-versus-react-there-will-be-blood-66595faafd51#.v4y4euy1r)，其实[之前有人翻译过](http://www.w3ctech.com/topic/1675?from=timeline&isappinstalled=0)，但是翻得水平有一点不忍直视，我们不希望浪费这篇好文章。  \n本文由 [@李凌豪](https://www.zhihu.com/people/li-ling-hao) [@黄玄](https://www.zhihu.com/people/huxpro) 联合翻译，首次发布于[前端外刊评论 · 知乎专栏](http://zhuanlan.zhihu.com/FrontendMagazine)，转载请保留原文链接 ;)\n"
  },
  {
    "path": "_posts/2016-06-05-pwa-in-my-pov.markdown",
    "content": "---\nlayout:     keynote\ntitle:      \"Progressive Web App 之我见\"\nsubtitle:   \"🎞  Slides:Progressive Web App, in my points of view\"\niframe:     \"//huangxuan.me/pwa-in-my-pov/\"\nnav-style:  \"invert\"\ndate:       2016-06-05\nauthor:     \"Hux\"\ntags:\n    - Slides\n    - Web\n    - PWA\n---\n\n\n> 下滑这里查看更多内容\n\n### [Watching Fullscreen →](https://huangxuan.me/pwa-in-my-pov/)\n\n<div class=\"visible-md visible-lg\">\n    <img src=\"//huangxuan.me/pwa-in-my-pov/attach/qrcode.png\" width=\"350\" />\n    <small class=\"img-hint\">Scanning on mobile</small>\n</div>\n\n\n### Catalog\n\n- WHAT is Progressive Web App?\n- 1 - Installability\n- 2 - App Shell\n- 3 - Offline\n    - SERVICE WORKER! \n- 4 - Re-engageable\n    - Push Notification\n- CONS in my pov\n- PROS in my pov\n- Why Web? \n\n\n### Power by [Yanshuo.io（演说.io）](https://yanshuo.io)\n"
  },
  {
    "path": "_posts/2016-09-22-the-open-web.md",
    "content": "---\nlayout: post\ntitle: \"Web 在继续离我们远去\"\nsubtitle: \"After the release of Wechat Mini-Program\"\nauthor: \"Hux\"\nheader-img: \"img/post-bg-web.jpg\"\nheader-mask: 0.4\ntags:\n  - Web\n  - 微信\n---\n\n> 本文首发于我的知乎专栏 [The Little Programmer](https://zhuanlan.zhihu.com/p/22561084)，转载请保留链接 ;)\n\n今天微信又刷爆了我的朋友圈 —— 小程序，之前传说的应用号。\n\n不过这篇不谈小程序的技术细节，也不去猜测（因为知道得很清楚……），\n\n也不谈小程序会对中国互联网带来什么影响（自有产品经理会来谈……），\n\n我们说说 Web，the Web。\n\n我们常说的 Web，其实是 World Wide Web 的简称 the Web 的简称。\n\n跟 H5 一样，这货是个简称的简称，所以简到最后就没人知道它本身是个什么意思了。\n\n不要说中国老百姓分不清万维网和互联网了，美国老百姓也一样分不清 Web 和 Internet，\n\n很多不求甚解的从业人士也好不到哪去，Web 常年在技术文章中被翻译得不知所云。\n\n中文世界里把这件事讲得最清楚也最在乎的，非 [@不鳥萬如一](//www.zhihu.com/people/6bec872206d9884cd9535841b6a1f510) 莫属了。\n\n比如在[《一天世界》博客：微信并不是在「管理」外部链接，因为微信公众号在事实上（de facto）不允许任何外部链接 - 不鳥萬通讯 - 知乎专栏](https://zhuanlan.zhihu.com/p/20747514) 里他写到：\n\n> 中文世界一直混淆[互联网](https://en.wikipedia.org/wiki/Internet)（internet）和[万维网](https://en.wikipedia.org/wiki/World_Wide_Web)（web）。人们念兹在兹的「互联网开放精神」，实乃万维网的开放精神。万维网的开放主要就体现在一点：**任何万维网上的文章之间都可以通过网址随意互相链接**。如果我想在文章里介绍 UbuWeb 这个网站，我就可以直接在 [UbuWeb](https://ubu.com/) 这六个字母上添加它的网址 ubu.com。妳或许觉得这是废话，但在微信公众号的文章里妳做不到；妳只能添加微信生态圈内的链接，比如这个：[https://weixin.qq.com/cgi-bin/readtemplate?t=weixin_external_links_content_management_specification](https://weixin.qq.com/cgi-bin/readtemplate%3Ft%3Dweixin_external_links_content_management_specification)（即上述《规范》的链接）\n\n所以如一卸了微信（ [告别微信 一天世界](https://blog.yitianshijie.net/2016/02/21/byebye-wechat/) ）还写了：[微信——事实上的局域网](https://blog.yitianshijie.net/2015/11/16/wechat-de-facto-lan/) ，嗯，作为一个愈发对 Open Web 这件事 hardcore 的人来说，我是认同的。\n\n如一最在乎的可能是文章，而我更在乎的是应用，Web App。\n\n所谓 Web App，是 Web 的一种进化：从提供文本信息（超文本）到多媒体（超媒体）到提供软件应用服务。硬核的翻译过来大概是“基于万维网的应用”，比如你在 Web 浏览器中使用的 Youtube、Twitter、Medium、Github 等等，**它们之间仍然可以通过网址（URL）随意互相链接，遵循 Web 开放标准，并且你几乎可以在任何一个具备浏览器的平台上使用这项服务，因此 Web App 同样是开放的。**\n\n如果你听说过 Google 的 Progressive Web Apps，它其实代表的是 Progressive Open Web Apps，只是这样实在太长太啰嗦了。\n\n毕竟，Web 的概念里理应包含着 Open。\n\n（这篇文章的本意并不是为了 advocate PWA，但如果你对 PWA 有兴趣，欢迎阅读： [黄玄：下一代 Web 应用模型 — Progressive Web App​zhuanlan.zhihu.com!](https://zhuanlan.zhihu.com/p/25167289)\n\n如果说 Hybrid 架构还只是 Web 理想主义的一次让步，那么 React Native 的出现则无疑让部分人的信仰崩塌，然后是 Weex，然后可能是你们猜的微信。\n\n眼见 “以 Web 范式为 Native 平台进行开发” 的方式越来越火，虽然受益的好像是 Web 前端从业人员，可我却不知该不该开心。\n\n我不是说它们是“错误的技术方向”，从实用主义来说它们很棒，很解决问题。\n\n**但是，无论他们长得有多像 Web，他们都不是 Open Web 平台的一员。**\n\nRN/Weex 根本没有 URL（别跟我说 Universal Links 或 App Links，URL 和 URI 是不同的）\n\n而微信从 JS-SDK 开始，便已经是一个封闭生态了。\n\n这种势头虽然缘起于 Facebook，却更可能在中国撒起野来。\n\n英文世界里对这类事情敏感/hardcore 的人很多，比如写了 [Regressive Web Apps](https://adactio.com/journal/10708) 的 Jeremy Keith，因为 PWA 对 URL 不够友好的事情跟 Chrome 开发老大 Alex 吵了一架，而 Alex 也急得说出了：\n\n> so, your choices are to think that I have a secret plan to kill URLs, or conclude I’m still Team Web.\n\n要知道，Alex 带着 Chrome 搞 PWA 的原因就是看不爽 Hybrid 破坏了 Open Web。\n\n倘若 Twitter/FB 跟微信一样连链接还不让随便链，大概都得弃用 Twitter，然后像如一一样火冒三丈的写一篇 Byebye Twitter/FB。\n\n而国内天天鼓吹得什么 XX 助力 HTML5 生态，却不知大部分时候这些所谓 “HTML5 生态” 都是和 Web 生态背道而驰的，高下立判。\n\n我开始有些语无伦次了。\n\n在这个 HTML5 与 Web 被极度误用的中文世界里，我也不知道该如何呐喊了。\n\n我只知道，当 Web 只能作为 Native 的 \"Markup Language\" 存活时，Web 也就不复存在了。\n\n当大家都不跟随 Web 标准自造一套时，Web 所谓的跨平台特性也就烟消云散了。\n\n我之前写过的，Chrome 产品 Leader Rahul 也在 I/O 上说过得：\n\nWeb 的 Dicoverable、Linkable、Low Friction、Broad Reach 等等，这些都不是 Web 的本质，**Web 的本质是 Open（开放）与 Decentralized （去中心化），这才是万维网（WWW）的初衷，这才是所有这些特性能成立的前提。**\n\nOpen Web 的信仰让浏览器厂商们重新走到了一起，他们在问你:\n\n**Hey, can we make the web great again?**\n"
  },
  {
    "path": "_posts/2016-10-20-pwa-qcon2016.markdown",
    "content": "---\nlayout:     keynote\ntitle:      \"Progressive Web Apps，复兴序章「QCon 上海 2016」\"\nsubtitle:   \"🎞  Slides:Progressive Web Apps, Make Web Great Again. (QCon Shanghai 2016)\"\niframe:     \"//huangxuan.me/pwa-qcon2016/\"\nnavcolor:   \"invert\"\ndate:       2016-10-20\nauthor:     \"Hux\"\ntags:\n    - Slides\n    - Web\n    - PWA\n---\n\n\n> 下滑这里查看更多内容\n\n\n### [Watching Fullscreen →](https://huangxuan.me/pwa-qcon2016/)\n\n### [Watching Video →](http://www.infoq.com/cn/presentations/progressive-web-app)\n\n<div class=\"visible-md visible-lg\">\n    <img src=\"//huangxuan.me/pwa-qcon2016/attach/qrcode.png\" width=\"350\" />\n    <small class=\"img-hint\">Scanning on mobile</small>\n</div>\n\n\n### Catalog\n\n- The State Of Web\n- Rethinking Hybridzation\n- PWA 101\n    - Definition\n    - Add To HomeScreen\n        - Web Manifest\n    - Reliable Experience (Network as PE)\n        - Service Worker\n            - Register SW\n            - On Install & Cache API\n                - ExtendableEvent & SkipWaiting\n            - On Fetch\n            - Stale-While-Revalidate & Fallback\n            - Updating SW\n            - SW LifeCycle\n            - On Activate\n            - SW Brings Architectural Revolution\n    - Re-engageable\n- PWA In Production \n    - User Expectation & Guiding\n    - Low Deliver Friction\n- PWA vs. Others\n- The Belief In Web\n    - One Web\n    - Fulfill WWDC 2007 \n\n\n### Notes  \n\nThis slides is powered by [Yanshuo.io (演说.io)](http://yanshuo.io), a online software helping you create, store and share web slides. \n\n`index.html` is the HTML source code exported from [Yanshuo.io](http://yanshuo.io), and many of its dependencis (js, css, fonts) are still linked to CDN of [Yanshuo.io](http://yanshuo.io). You can do any secondary development and host it by yourself.\n\n\n"
  },
  {
    "path": "_posts/2016-11-20-sw-101-gdgdf.markdown",
    "content": "---\nlayout:     keynote\ntitle:      \"Service Worker 101「GDG DevFest 2016 北京」\"\nsubtitle:   \"🎞  Slides:Service Worker 101, Working Offline and Instant Loading (GDG DevFest 2016 Beijing)\"\niframe:     \"//huangxuan.me/sw-101-gdgdf/\"\nnavcolor:   \"invert\"\ndate:       2016-11-20\nauthor:     \"Hux\"\ntags:\n    - Slides\n    - Web\n    - PWA\n---\n\n\n> 下滑这里查看更多内容\n\n\nTLDR; It covers lots of cool stuff about Service Worker!\n\n### [Watching Fullscreen → ](https://huangxuan.me/sw-101-gdgdf/)\n\n<div class=\"visible-md visible-lg\">\n    <img src=\"//huangxuan.me/sw-101-gdgdf/attach/qrcode.png\" width=\"350\" />\n    <small class=\"img-hint\">Scanning on mobile</small>\n</div>\n\n\n\n### [Demo Code → ](https://github.com/Huxpro/sw-101-gdgdf)\n\n- Hello World of Service Worker\n- Make your own Offline Dinosaurs\n- Stale/Fastest while revalidate\n\n\n\n### Notes  \n\nThis slides is powered by [Yanshuo.io (演说.io)](http://yanshuo.io), a online software helping you create, store and share web slides. \n\nThere are 2 ways that you can fork or contribute this project:\n\n1. `index.html` is the HTML source code exported from [Yanshuo.io](http://yanshuo.io), and many of its dependencis (js, css, fonts) are still linked to CDN of [Yanshuo.io](http://yanshuo.io). You can do any secondary development and host it by yourself.\n2. Download the project file under `shuo/`, drag it into [Yanshuo.io](http://yanshuo.io), and you are ready to go. You can edit whatever you want, upload it to your account, and even share your distributions.\n\n\n"
  },
  {
    "path": "_posts/2017-01-09-wechat-miniapp-ux.md",
    "content": "---\nlayout: post\ntitle: \"如何客观地评价「小程序」的体验?\"\nsubtitle: \"Wechat Mini-Program vs. the Web, a UX comparison\"\nauthor: \"Hux\"\nheader-img: \"\"\nheader-bg-css: \"linear-gradient(to right, #24b94a, #38ef7d);\"\ntags:\n  - Web\n  - 微信\n  - UX/UI\n---\n\n> 本文首发于我的知乎专栏 [The Little Programmer](https://zhuanlan.zhihu.com/p/24782839)，转载请保留链接 ;)\n\n2017 年 1 月 9 号凌晨，看完《星战》回家，发现朋友圈都炸了……原来是「小程序」如约公测（以下简称小程序）。果然贵圈人都睡得晚啊，一个个大半夜了精神得不行。\n\n截图推荐什么的已经漫天都是了，而且连 「推荐小程序的小程序」都已经出现了，我们就直入正题吧，今天笔者不跟你们聊情怀，就聊体验：\n\n  \n\n### **小程序的体验比 Web 更好吗？**\n\n体验完利益相关微票儿的「电影演出赛事」后，我在朋友圈里怒发了一条「实际体验小程序的感觉就是**完全没有**比普通的 web 体验更好…」，感觉评论里 [@Cheeeee](//www.zhihu.com/people/11b8c6f61424152bd0e6d8a97760df16) 都受惊了 > <。不过在多体验了几个小程序之后，我觉得我应该尝试更客观的回答这个问题。\n\n### 1\\. 在「微信」里，小程序的 Engagement 比 Web 更好。\n\n这当然是毋庸置疑的，我的博客在微信里打开至今都是「非微信官方网页，继续访问将转换成手机预览模式」，然后点击「继续访问」就是「params is error」，我 &^\\*#.%...\n\n而对于其他在微信中可访问的 web 应用来说，小程序有着自己的搜索入口、抽屉（历史记录），还可以「显示在聊天顶部」，这其实分别对应着 「拉新」、「包活」 与一定的「多任务」支持。尤其是后两者，与 [PWA](https://www.zhihu.com/question/46690207/answer/104851767) 的「添加至主屏」与「出现在 Task Switcher」里异曲同工。\n\n正如 [微信小程序和网页版程序的区别在哪里？ - 冯雨的回答 - 知乎](https://www.zhihu.com/question/54148303/answer/138152983) 里所说的，「订阅号、服务号、小程序，就是一个个静态或动态的 Web站点；二维码和消息气泡，一个现实一个虚拟，就是微信提供的超级链接。」\n\n**World Wide Web 在微信里是残废的，取而代之的是 Weixin/Wechat Wide Web。**\n\n值得一提的是，现在微信只会对特定小程序支持模糊搜索，而且据我目测都是诸如京东、滴滴这样的「国家队」。喏，在我地盘这你就得听我的~ ? ?\n\n### 2\\. 在「微信」里，小程序的 Capability 比 Web 更好。\n\n当我们在说「小程序的体验是否能比 Web 更接近原生应用」时，我们通常指的就是它的 capability。\n\n先说 UI 性能，截止目前为止，小程序的大部分组件都还是使用 WebView 渲染的，这意味着在大多数组件场景下，小程序的 UI 性能不可能比 Web 更高。但是：\n\n1.  **小程序团队非常 tricky 地把力气都用在了刀刃上**：每一个使用原生 UI 渲染、或在自定义 WebView 中优化过的组件都对应着 Mobile Web 中的一个老大难问题。比如在 iOS 上让顶部或底部的 Tab Bar \"Fixed\"，比如视频的自动播放与控制力，比如地图、textarea 等，可以说利用有限的资源显著提高了小程序的可用性。\n2.  由于 Web 前端开发者的良莠不齐，小程序通过限定一组 Web 技术的子集，可以很好的约束开发者写出性能与体验不低于基线的代码，这与 Google 的 AMP 异曲同工。（其实这是大家觉得小程序体验比 Web 好的很大一个原因）  \n    \n3.  由于小程序中的 wxml 与 wxss 都是比较 high-level 的抽象，所以微信团队可以在不影响开发者源代码的情况下，通过升级 Runtime 与组件的实现不断优化小程序的性能，比如完全迁移到类似 React Native 或 Weex 这样的 JS-to-Native 方案。\n\n  \n\n再说启动性能，这是让大家觉得小程序感知体验比 Web 好的第二个大因素：\n\n1.  由于小程序是打包部署并「安装」的，可以从文件系统中直接启动。以此解决 web 带来的网络延迟与离线时不可访问问题。\n\n  \n最后是 Integration。通过私有的 JS SDK，小程序可以借助微信这座桥梁实现很多以往 Web 并不容易实现的体验。同样，这些改进也非常 tricky，只解决痛点问题：\n\n1.  设备访问能力，文件、系统、网络、GPS、加速计、罗盘……\n2.  「第一公民」能力，最明显的莫过于设置导航条和页与页之间的动画。还有 Android 设备上的「添加小程序到桌面」，其实就是个快捷方式。\n\n![](/img/in-post/post-wmu/maoyan.jpg)\n\n（图为猫眼 App 与小程序，因为长得像…感觉不小心给老东家竞争对手打广告了?）  \n\n**可惜的是，这些技术里面没有一项是「小程序」首创的，且大都有超过两年的历史：**百度的 Blend UI、阿里的 Hybrid 容器、Google 的 PWA/AMP、Phonegap/Cordova、React Native/Weex……这也是很多技术从业人吐槽小程序在技术上毫无创新的原因。\n\n但平心而论，崇尚「技术服务产品」的腾讯系在产品化上做的真心出色。这也是我为什么在 9 月 21 日知道小程序技术方案时夸赞「兼容并蓄 博采众长 且可持续性发展」的原因，并不是站在技术创新的角度，而是站在微信的角度上，这个决策拿捏在了 sweet point 上。\n\n### 3\\. 在「微信」里，小程序不一定比 Web 更好的。\n\n目前我所了解到的（截止 2017 年 1 月 9 日）：\n\n  \n\n1.  小程序对比 Web，只能通过摄像头扫码，不能分享朋友圈，营销难做，这是 Reach。  \n    \n2.  小程序中没有真正的超链接与 WebView，完全不能外链，这是 Linkability。如果知乎要做小程序，所有答案里的超链接都只能报废。或者只能像轻芒杂志那样，做一层转码，美其名曰阅读模式。\n3.  小程序目前的组件虽然 cover 了大部分场景，但是也明显有很多不能 cover 到的 case，这是 Scalability。\n\n这三点都是可以直接影响到目前小程序的产品形态与设计的。当然，对于微信来说，这三点更多的是决策问题。作为 Weixin Wide Web 这个封闭生态的唯一「浏览器」，微信便是生杀大权。手起刀落之间，小程序的缺点随时可以被弥补，而 Web 的优点也随时可以被抹杀。\n\n  \n\n但是，现实可能并不会这么简单。我们发现，大部分小程序都只提供了其原生应用或 web 应用功能的一个子集。比如文章最早提到的微票儿的「电影演出赛事」小程序，与钱包里的 web 版本相比，UI 体验好了一点，但是功能远没有 web 版本来得丰富，也没有了 web 版本可以分享评论到朋友圈的能力。\n\n![](/img/in-post/post-wmu/wepiao.jpg)\n\n（微票儿小程序与其钱包中的内嵌 web 应用对比，web 版的功能要丰富得多。)\n\n微票儿（娱票儿）作为一家在微信里内嵌 web 服务起家的公司，一是证明了微信流量红利的可怕，二其实也证明了原有 web 的能力。作为「亲腾讯亲微信」的公司之一，其小程序比不上 web 应用可能只是时间关系。但是对于其他公司呢，尤其是未被腾讯「临幸」过的公司？而这其实对应着另一个更难回答的问题：\n\n  \n\n### **小程序值得接入商花多大的力气？**\n\n笔者自知无法回答这个问题，所以只能抛砖引玉一下：\n\n| N/A      | 简单体验（页面）  | 中等体验（mini-app） | 核心体验（app） |\n| -------- | ----------------- | -------------------- | --------------- |\n| 纯微信流 | 公众号 小程序     | 小程序               | 我想要个原生啊  |\n| 创业公司 | 公众号 Web 小程序 | Web 小程序           | 拉回原生啊      |\n| 中大公司 | 公众号 Web 小程序 | Web 小程序           | 拉回原生啊      |\n| 巨头公司 | 公众号 Web        | 不跟微信玩           | 不跟微信玩      |\n\n\n具体到每一个 Web 与小程序 PK 的场景：\n\n*   对于简单体验，小程序的一点点体验提升对比 Web 的跨平台与传播能力没有优势\n*   对于中等体验，小程序体验更好，但需要付出额外的人力资源与开发维护成本\n*   对于核心体验，大家的目标都是拉回自己的主场\n\n如果说阿里的「让天下没有难做的生意」是把话说开来「双赢」，微信「开放」平台和接入商之间的资源互换关系则更像是「权力的游戏」了：**微信想借接入商来建立自己的垄断帝国，接入商却想玩暗度陈仓。**某种程度上来说，Web 应用是自己的领地，值得在上面建立完整的体验。而小程序，可能会如小程序诞生前的「weixin-specific web」一样，很大程度上沦为拉新立牌坊的工具。\n\n所以我们不妨再加一条：\n\n4\\. 对于用户来说，小程序可能并不会「够用」，这是 Feature Set。\n\n### 4\\. 不在「微信」里，小程序……\n\n![](/img/in-post/post-wmu/question.jpg)\n\n### 5\\. 结论？\n\n回到问题「小程序的体验比 Web 更好吗？」，我觉得各位看官心里应该会有自己的答案。对于不同的公司，不同的业务场景，不同的盈利方式，不同的团队，我相信这个答案都是不一样的。\n\n> But if you trade something off, make sure you get something in return.  \n> 如果你需要妥协掉一些东西，请务必换回点好处来。\n\n作为一篇「试图做到客观（且非常难）」的文章，如果能对你有帮助，那就算没有白写了。\n\n### 6\\. 题外话（这段主观！）\n\n最后说两句题外话吧，上个月给《程序员》杂志交了拖了 N 久的稿，大概在本月底会发吧？\n\n在那篇文章最后我写到，「笔者奢望着本文能对推动 PWA 的国内环境有一定的贡献」。眼见小程序在某种意义上 \"polyfill\" （大雾）了 PWA，作为一个在技术上略有 [理想主义](https://zhuanlan.zhihu.com/p/22561084) 的程序员，笔者也只能叹一句了：\n\n「这不是我想要的未来。」\n\n会是你们的吗?\n"
  },
  {
    "path": "_posts/2017-02-09-nextgen-web-pwa.markdown",
    "content": "---\nlayout:     post\ntitle:      \"下一代 Web 应用模型 —— Progressive Web App\"\nsubtitle:   \"The Next Generation Application Model For The Web - Progressive Web App\"\ndate:       2017-02-09 12:00:00\nauthor:     \"Hux\"\nheader-img: \"img/post-bg-nextgen-web-pwa.jpg\"\nheader-mask: 0.3\ncatalog:    true\ntags:\n    - Web\n    - PWA\n---\n\n\n> 今年 9 月份的时候，《程序员》杂志社就邀请我写一篇关于 PWA 的文章。后来花式拖稿，拖过了 10 月的 QCon，11 月的 GDG DevFest，终于在 12 月把这篇长文熬了出来。几次分享的不成熟，这次的结构算是比较满意了。「 可能是目前中文世界里对 PWA 最全面详细的长文了」，希望你能喜欢。<br><br>\n> 本文首发于 [CSDN](http://geek.csdn.net/news/detail/135595) 与《程序员》2017 年 2 月刊，同步发布于 [Hux Blog](https://huangxuan.me)、[前端外刊评论 - 知乎专栏](https://zhuanlan.zhihu.com/FrontendMagazine)，转载请保留链接 ;)\n\n\n## 下一代 Web 应用？\n\n近年来，Web 应用在整个软件与互联网行业承载的责任越来越重，软件复杂度和维护成本越来越高，Web 技术，尤其是 Web 客户端技术，迎来了爆发式的发展。\n\n包括但不限于基于 Node.js 的前端工程化方案；诸如 Webpack、Rollup 这样的打包工具；Babel、PostCSS 这样的转译工具；TypeScript、Elm 这样转译至 JavaScript 的编程语言；React、Angular、Vue 这样面向现代 web 应用需求的前端框架及其生态，也涌现出了像[同构 JavaScript][1]与[通用 JavaScript 应用][2]这样将服务器端渲染（Server-side Rendering）与单页面应用模型（Single-page App）结合的 web 应用架构方式，可以说是百花齐放。\n\n但是，Web 应用在移动时代并没有达到其在桌面设备上流行的程度。究其原因，尽管上述的各种方案已经充分利用了现有的 JavaScript 计算能力、CSS 布局能力、HTTP 缓存与浏览器 API 对当代基于 [Ajax][3] 与[响应式设计][4]的 web 应用模型的性能与体验带来了工程角度的巨大突破，我们仍然无法在不借助原生程序辅助浏览器的前提下突破 web 平台本身对 web 应用固有的桎梏：**客户端软件（即网页）需要下载所带来的网络延迟；与 Web 应用依赖浏览器作为入口所带来的体验问题。**\n\n![](/img/in-post/post-nextgen-web-pwa/PWAR-007.jpeg)\n*Web 与原生应用在移动平台上的使用时长对比 [图片来源: Google][i2]*\n\n在桌面设备上，由于网络条件稳定，屏幕尺寸充分，交互方式趋向于多任务，这两点造成的负面影响对比 web 应用免于安装、随叫随到、无需更新等优点，瑕不掩瑜。但是在移动时代，脆弱的网络连接与全新的人机交互方式使得这两个问题被无限放大，严重制约了 web 应用在移动平台的发展。在用户眼里，原生应用不会出现「白屏」，清一色都摆在主屏幕上；而 web 应用则是浏览器这个应用中的应用，使用起来并不方便，而且加载也比原生应用要慢。\n\nProgressive Web Apps（以下简称 PWA）以及构成 PWA 的一系列关键技术的出现，终于让我们看到了彻底解决这两个平台级别问题的曙光：能够显著提高应用加载速度、甚至让 web 应用可以在离线环境使用的 Service Worker 与 Cache Storage；用于描述 web 应用元数据（metadata）、让 web 应用能够像原生应用一样被添加到主屏、全屏执行的 Web App Manifest；以及进一步提高 web 应用与操作系统集成能力，让 web 应用能在未被激活时发起推送通知的 Push API 与 Notification API 等等。\n\n将这些技术组合在一起会是怎样的效果呢？「印度阿里巴巴」 —— [Flipkart][17] 在 2015 年一度关闭了自己的移动端网站，却在年底发布了现在最为人津津乐道的 PWA 案例 *FlipKart Lite*，成为世界上第一个支撑大规模业务的 PWA。发布的一周后它就亮相于 [Chrome Dev Summit 2015][15] 上，笔者当时就被惊艳到了。为了方便各媒介上的读者观看，笔者做了几幅图方便给大家介绍：\n\n![](/img/in-post/post-nextgen-web-pwa/flipkart-1.jpeg)\n*图片来源: Hux & [Medium.com][i3]*\n\n当浏览器发现用户[需要][16] Flipkart Lite 时，它就会提示用户「嘿，你可以把它添加至主屏哦」（用户也可以手动添加）。这样，Flipkart Lite 就会像原生应用一样在主屏上留下一个自定义的 icon 作为入口；与一般的书签不同，当用户点击 icon 时，Flipkat Lite 将直接全屏打开，不再受困于浏览器的 UI 中，而且有自己的启动屏效果。\n\n\n![](/img/in-post/post-nextgen-web-pwa/flipkart-2.jpeg)\n*图片来源: Hux & [Medium.com][i3]*\n\n更强大的是，在无法访问网络时，Flipkart Lite 可以像原生应用一样照常执行，还会很骚气的变成黑白色；不但如此，曾经访问过的商品都会被缓存下来得以在离线时继续访问。在商品降价、促销等时刻，Flipkart Lite 会像原生应用一样发起推送通知，吸引用户回到应用。\n\n**无需担心网络延迟；有着独立入口与独立的保活机制。**之前两个问题的一并解决，宣告着 web 应用在移动设备上的浴火重生：满足 PWA 模型的 web 应用，将逐渐成为移动操作系统的一等公民，并将向原生应用发起挑战与「复仇」。\n\n更令笔者兴奋的是，就在今年 11 月的 [Chrome Dev Summit 2016][18] 上，Chrome 的工程 VP Darin Fisher 介绍了 Chrome 团队正在做的一些实验：把「添加至主屏」重命名为「安装」，被安装的 PWA 不再仅以 widget 的形式显示在桌面上，而是真正做到与所有原生应用平级，一样被收纳进应用抽屉（App Drawer）里，一样出现在系统设置中 🎉🎉🎉。\n\n![](/img/in-post/post-nextgen-web-pwa/flipkart-3.jpeg)\n*图片来源: Hux & [@adityapunjani][i4]*\n\n图中从左到右分别为：类似原生应用的安装界面；被收纳在应用抽屉里的 Flipkart Lite 与 Hux Blog；设置界面中并列出现的 Flipkart 原生应用与 Flipkart Lite PWA （可以看到 PWA 巨大的体积优势）\n\n**笔者相信，PWA 模型将继约 20 年前横空出世的 Ajax 与约 10 年前风靡移动互联网的响应式设计之后，掀起 web 应用模型的第三次根本性革命，将 web 应用带进一个全新的时代。**\n\n## PWA 关键技术的前世今生\n\n### [Web App Manifest][spec1]\n\nWeb App Manifest，即通过一个清单文件向浏览器暴露 web 应用的元数据，包括名字、icon 的 URL 等，以备浏览器使用，比如在添加至主屏或推送通知时暴露给操作系统，从而增强 web 应用与操作系统的集成能力。\n\n让 web 应用在移动设备上的体验更接近原生应用的尝试其实早在 2008 年的 [iOS 1.1.3 与 iOS 2.1.0 ][q37]时就开始了，它们分别为 web 应用增加了对自定义 icon 和全屏打开的支持。\n\n![](/img/in-post/post-nextgen-web-pwa/ios2-a2hs.gif)\n*图片来源: [appleinsider.com][i1]*\n\n但是很快，随着越来越多的私有平台通过 `<meta>`/`<link>` 标签来为 web 应用添加「私货」，`<head>` 很快就被塞满了：\n\n```html\n<!-- Add to homescreen for Safari on iOS -->\n<meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n<meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black\">\n<meta name=\"apple-mobile-web-app-title\" content=\"Lighten\">\n\n<!-- Add to homescreen for Chrome on Android -->\n<meta name=\"mobile-web-app-capable\" content=\"yes\">\n<mate name=\"theme-color\" content=\"#000000\">\n\n<!-- Icons for iOS and Android Chrome M31~M38 -->\n<link rel=\"apple-touch-icon-precomposed\" sizes=\"144x144\" href=\"images/touch/apple-touch-icon-144x144-precomposed.png\">\n<link rel=\"apple-touch-icon-precomposed\" sizes=\"114x114\" href=\"images/touch/apple-touch-icon-114x114-precomposed.png\">\n<link rel=\"apple-touch-icon-precomposed\" sizes=\"72x72\" href=\"images/touch/apple-touch-icon-72x72-precomposed.png\">\n<link rel=\"apple-touch-icon-precomposed\" href=\"images/touch/apple-touch-icon-57x57-precomposed.png\">\n\n<!-- Icon for Android Chrome, recommended -->\n<link rel=\"shortcut icon\" sizes=\"196x196\" href=\"images/touch/touch-icon-196x196.png\">\n\n<!-- Tile icon for Win8 (144x144 + tile color) -->\n<meta name=\"msapplication-TileImage\" content=\"images/touch/ms-touch-icon-144x144-precomposed.png\">\n<meta name=\"msapplication-TileColor\" content=\"#3372DF\">\n\n<!-- Generic Icon -->\n<link rel=\"shortcut icon\" href=\"images/touch/touch-icon-57x57.png\">\n```\n\n显然，这种做法并不优雅：分散又重复的元数据定义多余且难以维持同步，与 html 耦合在一起也加重了浏览器检查元数据未来变动的成本。与此同时，社区里开始出现使用 manifest 文件以中心化地描述元数据的方案，比如 [Chrome Extension、 Chrome Hosted Web Apps (2010)][12] 与 [Firefox OS App Manifest (2011)][13] 使用 JSON；[Cordova][19] 与 [Windows Pinned Site][20] 使用 XML；\n\n2013 年，W3C WebApps 工作组开始对基于 JSON 的 Manifest 进行标准化，于同年年底发布[第一份公开 Working Draft][14]，并逐渐演化成为今天的 W3C Web App Manifest：\n\n```json\n{\n  \"short_name\": \"Manifest Sample\",\n  \"name\": \"Web Application Manifest Sample\",\n  \"icons\": [{\n      \"src\": \"launcher-icon-2x.png\",\n      \"sizes\": \"96x96\",\n      \"type\": \"image/png\"\n   }],\n  \"scope\": \"/sample/\",\n  \"start_url\": \"/sample/index.html\",\n  \"display\": \"standalone\",\n  \"orientation\": \"landscape\"\n  \"theme_color\": \"#000\",\n  \"background_color\": \"#fff\",\n}\n```\n```html\n<!-- document -->\n<link rel=\"manifest\" href=\"/manifest.json\">\n```\n\n诸如 `name`、`icons`、`display` 都是我们比较熟悉的，而大部分新增的成员则为 web 应用带来了一系列以前 web 应用想做却做不到（或在之前只能靠 hack）的新特性：\n\n- `scope`：定义了 web 应用的浏览作用域，比如作用域外的 URL 就会打开浏览器而不会在当前 PWA 里继续浏览。\n- `start_url`：定义了一个 PWA 的入口页面。比如说你添加 [Hux Blog][21] 的任何一个文章到主屏，从主屏打开时都会访问 [Hux Blog][21] 的主页。\n- `orientation`：终于，我们可以锁定屏幕旋转了（喜极而泣…）\n- `theme_color`/`background_color`：主题色与背景色，用于配置一些可定制的操作系统 UI 以提高用户体验，比如 Android 的状态栏、任务栏等。\n\n这个清单的成员还有很多，比如用于声明「对应原生应用」的 `related_applications` 等等，本文就不一一列举了。作为 PWA 的「户口本」，承载着 web 应用与操作系统集成能力的重任，Web App Manifest 还将在日后不断扩展，以满足 web 应用高速演化的需要。\n\n\n\n### [Service Worker][spec2]\n\n我们原有的整个 Web 应用模型，都是构建在「用户能上网」的前提之下的，所以一离线就只能玩小恐龙了。其实，对于「让 web 应用离线执行」这件事，Service Worker 至少是 web 社区的第三次尝试了。\n\n故事可以追溯到 2007 年的 [Google Gears][48]：为了让自家的 Gmail、Youtube、Google Reader 等 web 应用可以在本地存储数据与离线执行，Google 开发了一个浏览器拓展来增强 web 应用。Google Gears 支持 IE 6、Safari 3、Firefox 1.5 等浏览器；要知道，那一年 Chrome 都还没出生呢。\n\n在 Gears API 中，我们通过向 LocalServer 模块提交一个缓存文件清单来实现离线支持：\n\n```javascript\n// Somewhere in your javascript\nvar localServer = google.gears.factory.create(\"bata.localserver\");\nvar store = localServer.createManagedStore(STORE_NAME);\nstore.manifestUrl = \"manifest.json\"\n```\n```js\n// manifest.json\n{\n  \"betaManifestVersion\": 1,\n  \"version\": \"1.0\",\n  \"entries\": [\n    { \"url\": \"index.html\" }, \n    { \"url\": \"main.js\" }\n  ]\n}\n```\n\n是不是感到很熟悉？好像 [HTML5 规范][spec11]中的 Application Cache 也是类似的东西？\n\n```html\n<html manifest=\"cache.appcache\">\n```\n```\nCACHE MANIFEST\n\nCACHE:\nindex.html\nmain.js\n```\n\n是的，Gears 的 LocalServer 就是后来大家所熟知的 App Cache 的前身，大约从 [2008][spec10] 年开始 W3C 就开始尝试将 Gears 进行标准化了；除了 LocalServer，Gears 中用于提供并行计算能力的 WorkerPool 模块与用于提供本地数据库与 SQL 支持的 Database 模块也分别是日后 Web Worker 与 Web SQL Database（后被废弃）的前身。\n\nHTML5 App Cache 作为第二波「让 web 应用离线执行」的尝试，确实也服务了比如 Google Doc、尤雨溪早年作品 HTML5 Clear、以及一直用 web 应用作为自己 iOS 应用的 FT.com（Financial Times）等不少 web 应用。那么，还有 Service Worker 什么事呢？  \n\n是啊，如果 App Cache 没有被设计得[烂到完全不可编程、无法清理缓存、几乎没有路由机制、出了 Bug 一点救都没有][s12]，可能就真没 Service Worker 什么事了。[App Cache 已经在前不久定稿的 HTML5.1 中被拿掉了，W3C 为了挽救 web 世界真是不惜把自己的脸都打肿了……][s13]\n\n时至今日，我们终于迎来了 Service Worker 的曙光。简单来说，Service Worker 是一个可编程的 Web Worker，它就像一个位于浏览器与网络之间的客户端代理，可以拦截、处理、响应流经的 HTTP 请求；配合随之引入 Cache Storage API，你可以自由管理 HTTP 请求文件粒度的缓存，这使得 Service Worker 可以从缓存中向 web 应用提供资源，即使是在离线的环境下。\n\n\n![](/img/in-post/post-nextgen-web-pwa/sw-sw.png)\n*Service Worker 就像一个运行在客户端的代理*\n\n比如说，我们可以给网页 `foo.html` 注册这么一个 Service Worker，它将劫持由 `foo.html` 发起的一切 HTTP 请求，并统统返回未设置 `Content-Type` 的 `Hello World!`：\n\n```javascript\n// sw.js\nself.onfetch = (e) => {\n  e.respondWith(new Response('Hello World!'))\n}\n```\n\nService Worker 第一次发布于 2014 年的 Google IO 上，目前已处于 W3C 工作草案的状态。其设计吸取了 Application Cache 的失败经验，作为 web 应用的开发者的你有着完全的控制能力；同时，它还借鉴了 Chrome 多年来在 Chrome Extension 上的设计经验（Chrome Background Pages 与 Chrome Event Pages），采用了基于「事件驱动」的唤醒机制，以大幅节省后台计算的能耗。比如上面的 `fetch` 其实就是会唤醒 Service Worker 的事件之一。\n\n![](/img/in-post/post-nextgen-web-pwa/sw-lifecycle.png)\n*Service Worker 的生命周期*\n\n除了类似 `fetch` 这样的功能事件外，Service Worker 还提供了一组生命周期事件，包括安装、激活等等。比如，在 Service Worker 的「安装」事件中，我们可以把 web 应用所需要的资源统统预先下载并缓存到 Cache Storage 中去：\n\n```javascript\n// sw.js\nself.oninstall = (e) => {\n  e.waitUntil(\n    caches.open('installation')\n      .then(cache =>  cache.addAll([\n        './',\n        './styles.css',\n        './script.js'\n      ]))\n  )\n});\n```\n\n这样，当用户离线，网络无法访问时，我们就可以从缓存中启动我们的 web 应用：\n\n```javascript\n//sw.js\nself.onfetch = (e) => {\n  const fetched = fetch(e.request)\n  const cached = caches.match(e.request)\n\n  e.respondWith(\n    fetched.catch(_ => cached)\n  )\n}\n```\n\n可以看出，Service Worker 被设计为一个相对底层（low-level）、高度可编程、子概念众多，也因此异常灵活且强大的 API，故本文只能展示它的冰山一角。出于安全考虑，注册 Service Worker 要求你的 web 应用部署于 HTTPS 协议下，以免利用 Service Worker 的中间人攻击。笔者在今年 GDG 北京的 DevFest 上分享了 [Service Worker 101][b0]，涵盖了 Service Worker 譬如「网络优先」、「缓存优先」、「网络与缓存比赛」这些更复杂的缓存策略、学习资料、以及[示例代码][29]，可以供大家参考。\n\n\n![](/img/in-post/post-nextgen-web-pwa/sw-race.png)\n*Service Worker 的一种缓存策略：让网络请求与读取缓存比赛*\n\n你也可以尝试在支持 PWA 的浏览器中访问笔者的博客 [Hux Blog][21]，感受 Service Worker 的实际效果：所有访问过的页面都会被缓存并允许在离线环境下继续访问，所有未访问过的页面则会在离线环境下展示一个自定义的离线页面。\n\n在笔者看来，**Service Worker 对 PWA 的重要性相当于 `XMLHTTPRequest` 之于 Ajax，媒体查询（Media Query）之于响应式设计，是支撑 PWA 作为「下一代 web 应用模型」的最核心技术。**由于 Service Worker 可以与包括 Indexed DB、Streams 在内的大部分 DOM 无关 API 进行交互，它的潜力简直无可限量。笔者几乎可以断言，Service Worker 将在未来十年里成为 web 客户端技术工程化的兵家必争之地，带来「离线优先（Offline-first）」的架构革命。\n\n\n\n### Push Notification\n\nPWA 推送通知中的「推送」与「通知」，其实使用的是两个不同但又相得益彰的 API：\n\n[Notification API][spec4] 相信大家并不陌生，它负责所有与通知本身相关的机制，比如通知的权限管理、向操作系统发起通知、通知的类型与音效，以及提供通知被点击或关闭时的回调等等，目前国内外的各大网站（尤其在桌面端）都有一定的使用。Notification API 最早应该是在 [2010][22] 年前后由 Chromium 提出[草案][spec7]以 `webkitNotifications` 前缀方式实现；随着 2011 年进入标准化；2012 年在 Safari 6（Mac OSX 10.8+）上获得支持；2015 年 Notification API 成为 [W3C Recommendation][spec8]；2016 年 [Edge 的支持][23]；Web Notifications 已经在桌面浏览器中获得了全面支持（Chrome、Edge、Firefox、Opera、Safari）的成就。\n\n[Push API][spec3] 的出现则让推送服务具备了向 web 应用推送消息的能力，它定义了 web 应用如何向推送服务发起订阅、如何响应推送消息，以及 web 应用、应用服务器与推送服务之间的鉴权与加密机制；由于 Push API 并不依赖 web 应用与浏览器 UI 存活，所以即使是在 web 应用与浏览器未被用户打开的时候，也可以通过后台进程接受推送消息并调用 Notification API 向用户发出通知。值得一提的是，Mac OSX 10.9 Mavericks 与 Safari 7 在 2013 年就发布了自己的私有推送支持，基于 APNS 的 [Safari Push Notifications][24]。\n\n在 PWA 中，我们利用 Service Worker 的后台计算能力结合 Push API 对推送事件进行响应，并通过 Notification API 实现通知的发出与处理：\n\n```javascript\n// sw.js\nself.addEventListener('push', event => {\n  event.waitUntil(\n    // Process the event and display a notification.\n    self.registration.showNotification(\"Hey!\")\n  );\n});\n\nself.addEventListener('notificationclick', event => {  \n  // Do something with the event  \n  event.notification.close();  \n});\n\nself.addEventListener('notificationclose', event => {  \n  // Do something with the event  \n});\n```\n\n对于 Push Notification，笔者的几次分享中一直都提的稍微少一些，一是因为 Push API 还处于 Editor Draft 的状态，二是目前浏览器与推送服务间的协议支持还不够成熟：Chrome（与其它基于 Blink 的浏览器）在 Chromium 52 之前只支持基于 Google 私有的 GCM/FCM 服务进行通知推送。不过好消息是，继 Firefox 44 之后，Chrome 52 与 Opera 39 也紧追其后实现了正在由 IETF 进行标准化的 [Web 推送协议（Web Push Protocol）][spec5]。\n\n\n如果你已经在使用 Google 的云服务（比如 Firebase），并且主要面向的是海外用户，那么在 web 应用上支持基于 GCM/FCM 的推送通知并不是一件费力的事情，笔者推荐你阅读一下 Google Developers 的[系列文章][25]，很多国外公司已经玩起来了。\n\n\n\n## 从 Hybrid 到 PWA，从封闭到开放\n\n2008 年，当移动时代来临，[唱衰移动 Web 的声音][q17]开始出现，而浏览器的进化并不能跟上时，来自 Nitobi 的 Brian Leroux 等人创造了 [Phonegap][10]，希望它能以 Polyfill 的形式、弥补目前浏览器与移动设备间的「鸿沟」，从此开启了[混合应用（Hybrid Apps）][26]的时代。\n\n几年间，[Adobe AIR][5]、[Windows Runtime Apps][6]、[Chrome Apps][7]、[Firefox OS][8]、[WebOS][9]、[Cordova/Phonegap][10]、[Electron][11] 以及国内比如微信、淘宝，无数的 Hybrid 方案拔地而起，让 web 开发者可以在继续使用 web 客户端技术的同时，做到一些只有原生应用才能做到的事情，包括访问一些设备与操作系统 API，给用户带来更加 「Appy」 的体验，以及进入 App Store 等等。\n\n![](/img/in-post/post-nextgen-web-pwa/qcon-hybridzation.png)\n*众多的 Hybrid 方案*\n\nPWA 作为一个涵盖性术语，与过往的这些或多或少通过私有平台 API 增强 web 应用的尝试最大的不同，在于构成 PWA 的每一项基本技术，都已经或正在被 IETF、ECMA、W3C 或 WHATWG 标准化，不出意外的话，它们都将被纳入开放 web 标准，并在不远的将来得到所有浏览器与全平台的支持。我们终于可以逃出 App Store 封闭的秘密花园，重新回到属于 web 的那片开放自由的大地。\n\n有趣的是，从上文中你也可以发现，组成 PWA 的各项技术的草案正是由上述各种私有方案背后的浏览器厂商或开发者直接贡献或间接影响的。可以说，PWA 的背后并不是某一家或两家公司，而是整个 web 社区与整个 web 规范。**正是因为这种开放与去中心化的力量，使得万维网（World Wide Web）能够成为当今世界上跨平台能力最强、且几乎是唯一一个具备这种跨平台能力的应用平台。**\n\n[「我们相信 Web，是因为相信它是解决设备差异化的终极方案；我们相信，当 Web 在今天做不到一件事的时候，是因为它还没来得及去实现，而不是因为他做不到。而 Phonegap，它的终极目的就是消失在 Web 标准的背后。」][27]\n\n在不丢失 web 的开放灵魂，在不需要依靠 Hybrid 把应用放在 App Store 的前提下，让 web 应用能够渐进式地跳脱出浏览器的标签，变成用户眼中的 App。这是 Alex Russell 在 2015 年提出 PWA 概念的[原委][28]。\n\n而又正因为 web 是一个整体，PWA 可以利用的技术远不止上述的几个而已：Ajax、响应式设计、JavaScript 框架、ECMAScript Next、CSS Next、Houdini、Indexed DB、Device APIs、Web Bluetooth、Web Socket、Web Payment、[孵化][spec6]中的 [Background Sync API][30]、[Streams][spec9]、WebVR……开放 Web 世界 27 年来的发展以及未来的一切，都与 PWA 天作之合。\n\n\n## 鱼与熊掌的兼得\n\n经过几年来的摸索，整个互联网行业仿佛在「Web 应用 vs. 原生应用」这个问题上达成了共识：\n\n- web 应用是鱼：迭代快，获取用户成本低；跨平台强体验弱，开发成本低。**适合拉新**。\n- 原生应用是熊掌：迭代慢，获取用户成本高；跨平台弱体验强，开发成本高。**适合保活**。\n\n要知道，虽然用户花在原生应用上的时间要明显多于 web 应用，但其中[有 80% 的时间是花在前五个应用中的][31]。[调查显示，美国有一半的智能手机用户平均每月新 App 安装量为零][32]，而月均网站访问量却有 100 个，更别提 Google Play 上[有 60% 的应用从未被人下载过了][33]。于是，整个行业的产品策略清一色地**「拿鱼换熊掌」**，比如笔者的老东家阿里旅行（飞猪旅行），web 应用布满阿里系各种渠道，提供「优秀的第一手体验」，等你用的开心了，再引诱你去下载安装原生应用。\n\n![](/img/in-post/post-nextgen-web-pwa/PWAR-014+PWA.jpeg)\n*原生应用、当代 Web 与 PWA 图片来源: Hux & [Google][i2]*\n\n但是，PWA 的出现，让鱼与熊掌兼得变成了可能 —— 它同时具备了 web 应用与原生应用的优点，有着自己独有的先进性：「浏览器 -> 添加至主屏/安装 -> 具备原生应用体验的 PWA -> 推送通知 -> 具备原生应用体验的 PWA」，PWA 自身就包含着从拉新到保活的闭环。\n\n除此之外，PWA 还继承了 web 应用的另外两大优点：**无需先付出几十兆的下载安装成本即可开始使用**，以及**不需要经过应用超市审核就可以发布新版本**。所以，PWA 可以称得上是一种「流式应用（Streamable App）」与「常青应用（Evergreen App）」\n\n\n## 未来到来了吗\n\n在笔者分享 PWA 的经历中，最不愿意回答的两个问题莫过于「PWA 已经被广泛支持了吗？」以及「PWA 与 ABCDEFG 这些技术方案相比有什么优劣？」，但是这确实是两个逃不开的问题。\n\n### PWA 的支持情况？\n\n当我们说到 PWA 是否被支持时，其实我们在说的是 PWA 背后的几个关键技术都得到支持了没有。以浏览器内核来划分的话，Blink（Chrome、Oprea、Samsung Internet 等）与 Gecko（Firefox）都已经实现了 PWA 所需的所有关键技术（👏👏👏），并已经开始探寻更多的可能性。EdgeHTML（Edge）[简直积极得不能更积极了][34]，所有的特性都已经处于「正在开发中」的[状态][35]。最大的绊脚石仍然来自于 Webkit（Safari），尤其是在 iOS 上，上述的四个 API 都未得到支持，而且由于平台限制，第三方浏览器也无法在 iOS 上支持。（[什么你说 IE？][42]）\n\n不过，也不要气馁，Webkit 不但在它 [2015 年发布的五年计划][36]里提到了 Service Worker，更是已经在最近实现了 Service Worker 所[依赖][41]的 Request、Response 与 Fetch API，还把 Service Worker 与 Web App Manifest 纷纷[列入了「正在考虑」][37]的 API 中；要知道，Webkit 可是把 Web Components 中的 HTML Imports 直接[列到「不考虑」里去了][38]……（其实 Firefox 也是）\n\n更何况，由于 web 社区一直以来所追求的「渐进增强、优雅降级」，一个 PWA 当然可以在 iOS 环境正常执行。[事实上，华盛顿邮报将网站迁移到 PWA 之后发现，不止是 Android，在 iOS 上也获得了 5 倍的活跃度增长][39]，（无论是不是它们之前的网站写得太烂吧），就算 iOS 现在还不支持 PWA 也[不会怎么样][40]，我们更是有理由相信 PWA 会很快在 iOS 上到来。\n\n### PWA vs. Others\n\n贺老（贺师俊）曾说过：「从纯 Web 到纯 Native，之间有许多可能的点」。当考虑移动应用的技术选型时，除了 Web 与原生应用，我们还有各种不同程度的 Hybrid，还有今年爆发的诸多 JS-to-Native 方案。\n\n虽然我在上文中用了「复仇」这样的字眼，不过无论从技术还是商业的角度，我们都没必要把 web 或是 PWA 放到 Native 的对立面去看。它们当然存在竞争关系，但是更多的时候，web-only 与 app-only 的策略都是不完美的，当公司资源足够的时候，我们通常会选择同时开发两者。[当然，无论与不与原生应用对比，PWA 让 web 应用变得体验更好这件事本身是毋庸置疑的。][43]「不谈场景聊技术都是扯淡」，[我们仍然还是需要根据自己产品与团队的情况来决定对应的技术选型与平台策略，只是 PWA 让 web 应用在面对选型考验时更加强势了而已。][44]\n\n\n![](/img/in-post/post-nextgen-web-pwa/qcon-trend.png)\n*众多的技术选型，以及笔者的一种猜测*\n\n笔者不负责任得做一些猜测：虽然[重量级的 Hybrid 架构与基础设施][45]仍是目前不少场景下最优的解决方案；但是随着移动设备本身的硬件性能提升与新技术的成熟与普及，JS-to-Native 与以 PWA 为首的纯 web 应用，将分别从两个方向挤压 Hybrid 的生存空间，消化当前 Hybrid 架构主要解决的问题；前者将逐渐演化为类似 Xarmarin 这样针对跨平台原生应用开发的解决方案；后者将显著降低当前 Hybrid 架构的容器开发与部署成本，将 Hybrid 返璞归真为简单的 webview 调用。\n\n这种猜测当然不是没有依据的瞎猜，比如前者可以参考阿里巴巴集团级别迁移 Weex 的战略与微信小程序的 roadmap；后者则可以参考当前 Cordova 与 Ionic 两大 Hybrid 社区对 PWA 的热烈反响。\n\n### PWA in China\n\n看看 Google 官方宣传较多的 PWA [案例][47]就会发现，FlipKart、Housing.com 来自印度；Lyft、华盛顿邮报来自北美；唯一来自中国的 AliExpress 主要开展的则是海外业务。\n\n由于中国的特殊性，笔者在[第一次][46]聊到 PWA 时难免表现出了一定程度的悲观：\n\n- 国内较重视 iOS，而 iOS 目前还不支持 PWA。\n- 国内的 Android 实为「安卓」，不自带 Chrome 是一，可能还会有其他兼容问题。\n- 国内厂商可能并不会像三星那样对推动自家浏览器支持 PWA 那么感兴趣。\n- 依赖 GCM 推送的通知不可用，Web Push Protocol 还没有国内的推送服务实现。\n- 国内 webview 环境较为复杂（比如微信），黑科技比较多。\n\n反观印度，由于 Google 服务健全、标配 Chrome 的 Android 手机市占率非常高，PWA 的用户达到率简直直逼 100%，也难免获得无数好评与支持了。**笔者奢望着本文能对推动 PWA 的国内环境有一定的贡献。**不过无论如何，PWA 在国内的春天可能的确会来得稍微晚一点了。\n\n\n## 结语\n\n「[我们信仰 Web，不仅仅在于软件、软件平台与单纯的技术][q97]，还在于[『任何人，在任何时间任何地点，都可以在万维网上发布任何信息，并被世界上的任何一个人所访问到。』而这才是 web 的最为革命之处，堪称我们人类，作为一个物种的一次进化。][27]」\n\n请不要让 web 再[继续离我们远去][49]，浏览器厂商们已经重新走到了一起，而下一棒将是交到我们 web 应用开发者的手上。[乔布斯曾相信 web 应用才移动应用的未来][50]，那就让我们用代码证明给这个世界看吧。\n\n**让我们的用户，也像我们这般热爱 web 吧。**\n\n黄玄，于 12 月的北京。\n\n---\n\n*注：在笔者撰文期间，Google 在 Google China Developers Days 上宣布了 developers.google.cn 域名的启用，方便国内开发者访问。对于文中所有链向 developers.google.com 的参考文献，应该都可以在 cn 站点中找到。*\n\n\n[1]: http://nerds.airbnb.com/isomorphic-javascript-future-web-apps/ \"Isomorphic JavaScript: The Future of Web Apps\"\n\n[2]: https://medium.com/@mjackson/universal-javascript-4761051b7ae9#.unrzyz3b2 \"Universal JavaScript\"\n\n[3]: https://en.wikipedia.org/wiki/Ajax_(programming) \"Ajax - Wikipedia\"\n\n[4]: https://en.wikipedia.org/wiki/Responsive_web_design \"Responsive Web Design - Wikipedia\"\n\n[5]: http://www.adobe.com/products/air.html \"Adobe AIR Application\"\n\n[6]: https://msdn.microsoft.com/en-us/library/windows/apps/br211385.aspx \"Windows Runtime JS API\"\n\n[7]: https://developer.chrome.com/extensions/apps \"Chrome Packaged Apps\"\n\n[8]: https://developer.mozilla.org/en-US/docs/Archive/Firefox_OS/Firefox_OS_apps/Building_apps_for_Firefox_OS \"Firefox OS Packaged Apps\"\n\n[9]: http://www.openwebosproject.org/ \"Open webOS\"\n\n[10]: https://cordova.apache.org/ \"Apache Cordova\"\n\n[11]: http://electron.atom.io/ \"Electron\"\n\n[12]: https://developer.chrome.com/extensions/manifest \"Chrome Apps Manifest\"\n\n[13]: https://developer.mozilla.org/en-US/docs/Archive/Firefox_OS/Firefox_OS_apps/Building_apps_for_Firefox_OS/Manifest \"Firefox OS App Manifest\"\n\n[14]: https://www.w3.org/TR/2013/WD-appmanifest-20131217/ \"Manifest for web apps and bookmarks - First Public Working Draft\"\n\n[15]: https://youtu.be/m2a9hlUFRhg \"Keynote (Chrome Dev Summit 2015)\"\n\n[16]: https://developers.google.com/web/fundamentals/engage-and-retain/app-install-banners/?hl=en \"Web App Install Banners - Google Developer\"\n\n[17]: https://en.wikipedia.org/wiki/Flipkart \"Flipkart - wikipedia\"\n\n[18]: https://youtu.be/eI3B6x0fw9s \"Keynote (Chrome Dev Summit 2016)\"\n\n[19]: http://cordova.apache.org/docs/en/6.x/config_ref/index.html \"Config.xml - Apache Cordova\"\n\n[20]: https://msdn.microsoft.com/en-us/library/dn320426%28v=vs.85%29.aspx \"Browser configuration schema reference - MSDN\"\n\n[21]: https://huangxuan.me \"Hux Blog\"\n\n[22]: https://www.html5rocks.com/en/tutorials/notifications/quick/ \"Using the Notification API\"\n\n[23]: https://blogs.windows.com/msedgedev/2016/05/16/web-notifications-microsoft-edge/#2VBm890EjvAvUcgE.97\n\n[24]: https://developer.apple.com/notifications/safari-push-notifications/ \"Safari Push Notifications\"\n\n[25]: https://developers.google.com/web/fundamentals/engage-and-retain/push-notifications/ \"Web Push Notifications - Google Developer\"\n\n[26]: https://en.wikipedia.org/wiki/Progressive_web_app#Hybrid_Apps\n\n[27]: http://phonegap.com/blog/2012/05/09/phonegap-beliefs-goals-and-philosophy/ \"PhoneGap Beliefs, Goals, and Philosophy\"\n\n[28]: https://infrequently.org/2015/06/progressive-apps-escaping-tabs-without-losing-our-soul/ \"Progressive Web Apps: Escaping Tabs Without Losing Our Soul\"\n\n[29]: https://github.com/Huxpro/sw-101-gdgdf\n\n[30]: developers.google.com/web/updates/2015/12/background-sync \"Background Sync - Google Developers\"\n\n[31]: http://marketingland.com/report-mobile-users-spend-80-percent-time-just-five-apps-116858 \"Report: Mobile Users Spend 80 Percent Of Time In Just Five Apps\"\n\n[32]: http://www.recode.net/2016/9/16/12933780/average-app-downloads-per-month-comscore \"Half of U.S. smartphone users download zero apps per month\"\n\n[33]: https://youtu.be/EUthgV-U05w \"AdWords for App Promotion - Google\"\n\n[34]: https://blogs.windows.com/msedgedev/2016/07/08/the-progress-of-web-apps/ \"The Progress of Web Apps - MSEdgeDev Blog\"\n\n[35]: https://developer.microsoft.com/en-us/microsoft-edge/platform/status/ \"Microsoft Edge web platform features status\"\n\n[36]: https://trac.webkit.org/wiki/FiveYearPlanFall2015\n\n[37]: https://webkit.org/status/ \"Webkit Feature Status\"\n\n[38]: https://webkit.org/status/#specification-web-components \"HTML Imports - Not Considering\"\n\n[39]: https://cloudfour.com/thinks/why-does-the-washington-posts-progressive-web-app-increase-engagement-on-ios/ \"Why does The Washington Post’s Progressive Web App increase engagement on iOS?\"\n\n[40]: https://cloudfour.com/thinks/ios-doesnt-support-progressive-web-apps-so-what/ \"iOS doesn’t support Progressive Web Apps, so what?\"\n\n[41]: https://jakearchibald.github.io/isserviceworkerready/ \"Is Service Worker Ready?\"\n\n[42]: https://www.microsoft.com/en-us/WindowsForBusiness/End-of-IE-support \"Internet Explorer End of Support\"\n\n[43]: https://cloudfour.com/thinks/progressive-web-apps-simply-make-sense/?utm_source=mobilewebweekly&utm_medium=email#fn-4857-1 \"Progressive Web Apps Simply Make Sense\"\n\n[44]: https://medium.com/@owencm/the-surprising-tradeoff-at-the-center-of-question-whether-to-build-an-native-or-web-app-d2ad00c40fb2#.ym83ct2ax \"The surprising tradeoff at the center of the question whether to build a Native or Web App\"\n\n[45]: http://zhihu.com/question/31316032/answer/75236718\n\n[46]: https://www.zhihu.com/question/46690207/answer/104851767\n\n[47]: https://developers.google.com/web/showcase/ \"Case Studies - Google Developers\"\n\n[48]: https://en.wikipedia.org/wiki/Google_Gears \"Gears - Wikipedia\"\n\n[49]: https://zhuanlan.zhihu.com/p/22561084 \"Web 在继续离我们远去\"\n\n[50]: youtu.be/y1B2c3ZD9fk?t=1h14m48s \"WWDC 2017\"\n\n\n[spec1]: https://w3c.github.io/manifest/#use-cases-and-requirements \"Web App Manifest\"\n\n[spec2]: https://w3c.github.io/ServiceWorker/ \"Service Worker\"\n\n[spec3]: http://w3c.github.io/push-api/ \"Push API\"\n\n[spec4]: https://notifications.spec.whatwg.org/ \"Notification API\"\n\n[spec5]: https://tools.ietf.org/html/draft-ietf-webpush-protocol-12 \"Web Push Protocol\"\n\n[spec6]: https://wicg.github.io/BackgroundSync/spec/ \"Web Background Synchronization - WICG\"\n\n[spec7]: http://www.chromium.org/developers/design-documents/desktop-notifications/api-specification \"API Specification - The Chromium Projects\"\n\n[spec8]: https://www.w3.org/TR/notifications/ \"Web Notifications - W3C\"\n\n[spec9]: https://streams.spec.whatwg.org/ \"Streams\"\n\n[spec10]: https://www.w3.org/TR/offline-webapps/ \"Offline Web Applications\"\n\n[spec11]: https://www.w3.org/TR/2011/WD-html5-20110525/offline.html \"HTML5 5.6 Offline Web Applications\"\n\n\n[i1]: http://appleinsider.com/articles/08/10/03/latest_iphone_software_supports_full_screen_web_apps.html\n\n[i2]: https://developers.google.com/web/events/pwaroadshow/\n\n[i3]: https://medium.com/@AdityaPunjani/building-flipkart-lite-a-progressive-web-app-2c211e641883#.hz4d3kw41 \"Building Flipkart Lite: A Progressive Web App\"\n\n[i4]: https://twitter.com/adityapunjani\n\n\n[q37]: https://huangxuan.me/pwa-qcon2016/#/37 \"PWA@QCon2016 #37\"\n\n[q17]: https://huangxuan.me/pwa-qcon2016/#/17 \"PWA@QCon2016 #17\"\n\n[q97]: https://huangxuan.me/pwa-qcon2016/#/99 \"PWA@QCon2016 #97\"\n\n[s12]: https://huangxuan.me/sw-101-gdgdf/#/12 \"SW-101@DevFest #12\"\n\n[s13]: https://huangxuan.me/sw-101-gdgdf/#/13 \"SW-101@DevFest #13\"\n\n[b0]: https://huangxuan.me/2016/11/20/sw-101-gdgdf/\n"
  },
  {
    "path": "_posts/2017-04-06-html-document.md",
    "content": "---\nlayout: post\ntitle: 如何理解 <code>document</code> 对象是 <code>HTMLDocument</code> 的实例？\nsubtitle: Why is <code>document</code> an instance of <code>HTMLDocument</code>?\nauthor: \"Hux\"\nheader-style: text\ntags:\n  - Web\n  - 知乎\n---\n\n> 这篇文章转载自[我在知乎上的回答](https://www.zhihu.com/question/57601873/answer/155685476)\n\n谢邀。\n\n首先要理解的是 DOM 是 API，是一组无关编程语言的接口（Interfaces）而非实现（Implementation）。前端平时常说的 DOM 其实只是浏览器通过 ECMAScript（JavaScript）对 DOM 接口的一种实现。\n\n其次要知道的是，DOM 既是为 HTML 制定的，也是为 XML 制定的。而两者各有一些特异的部分，所以作为 DOM 标准基石的 DOM Level 1 其实分为 Core 与 HTML 两个部分。Core 定义了 fundamental interfaces 与 extended interfaces，分别是共用的基础接口与 「XML 拓展包」，而 HTML 部分则全都是「HTML 拓展包」。题主所问到的 Document 接口被定义在 Core 的 fundamental interfaces 中，而 HTMLDocument 接口则定义在 HTML 部分中，且「接口继承」于 Document。\n\n这种继承关系当然是可以在 JavaScript 的 DOM 实现中体现出来的：\n\n```js\n// document 是 HTMLDocument 的实例\ndocument instanceof HTMLDocument // true\n\n// document 的 [[prototype]] 指向 HTMLDocument 的原型\ndocument.__proto__ === HTMLDocument.prototype // true\n\n// HTMLDocument 伪类继承于 Document\nHTMLDocument.prototype instanceof Document // true\nHTMLDocument.prototype.__proto__ === Document.prototype // true\n```\n\n至于 Document 与 HTMLDocument 这两个构造函数，跟 Array、Object 一样都是 built-in 的：\n\n```js\n> Document\n< function Document() { [native code] }\n> HTMLDocument\n< function HTMLDocument() { [native code] }\n```\n\n虽然是 native code，但一个有意思的现象是，这两个构造函数之间也是存在原型链的：\n\n```js\n// HTMLDocument 的 [[prototype]] 是指向 Document 的\nHTMLDocument.__proto__ == Document\n\n// 同理\nDocument.__proto__ == Node\nNode.__proto__ == EventTarget\n```\n\n其作用是实现对静态成员的继承。（ ES6 Class 的行为与此完全一致，但这个行为在更早之前就是这样了。）\n\n好了扯远了，总结一下，**在 JavaScript 的 DOM 实现中**\n\n*   document 是 HTMLDocument 的实例\n*   HTMLDocument 继承于 Document\n\n留一个课后作业，有兴趣的话可以看看 Document.prototype 与 HTMLDocument.prototype 里分别都有什么？在不同浏览器里都试试。\n\n以上。\n"
  },
  {
    "path": "_posts/2017-05-28-sw-precache.md",
    "content": "---\nlayout: post\ntitle: How does SW-Precache works?\nauthor: \"Hux\"\nheader-style: text\nlang: en\ntags:\n  - Web\n  - PWA\n  - En\n---\n\n[_SW-Precache_](https://github.com/GoogleChrome/sw-precache) _is a great Service Worker tool from Google. It is a node module designed to be_ _integrated_ _into your build process and to generate a service worker for you._ _Though_ _you can use sw-precache out of the box, you might still wonder what happens under the hood. There you go, this article is written for you!_\n\n> This post was first published at [Medium](https://medium.com/@Huxpro/how-does-sw-precache-works-2d99c3d3c725)\n\n## Overview\n\nThe core files involving in sw-precache are mainly three:\n\n```\nservice-worker.tmpl  \nlib/  \n ├ sw-precache.js  \n └ functions.js\n```\n\n`sw-precache.js` is the main entry of the module. It reads the configuration, processes parameters, populates the `service-worker.tmpl` template and writes the result into specified file. And`functions.js` is just a module containing bunch of external functions which would be all injected into the generated service worker file as helpers.\n\nSince the end effect of sw-precache is performed by the generated service worker file in the runtime, a easy way to get an idea of what happens is by checking out source code inside `service-worker.tmpl` . It’s not hard to understand the essentials and I will help you.\n\n## Initialization\n\nThe generated service worker file (let’s call it `sw.js` for instance) get configuration by text interpolation when `sw-precache.js` populating `service-worker.tmpl` .\n\n```js\n// service-worker.tmpl  \nvar precacheConfig = <%= precacheConfig %>;\n\n// sw.js  \nvar precacheConfig = [  \n  [\"js/a.js\", \"3cb4f0\"],   \n  [\"css/b.css\", \"c5a951\"]  \n]\n```\n\nIt’s not difficult to see that it’s a list of relative urls and MD5 hashes. In fact, one thing that `sw-precache.js` do in the build time is to calculate hash of each file that it asked to “precache” from `staticFileGlobs` parameter.\n\nIn `sw.js`, `precacheConfig` would be transformed into a ES6 Map with structure `Map {absoluteUrl => cacheKey}` as below. Noticed that I omit the origin part (e.g. `http://localhost`) for short.\n\n```js\n> urlToCacheKeys  \n< Map(2) {  \n  \"http.../js/a.js\" => \"http.../js/a.js?_sw-precache=3cb4f0\",   \n  \"http.../css/b.js\" => \"http.../css/b.css?_sw-precache=c5a951\"  \n}\n```\n\nInstead of using raw URL as the cache key, sw-precache append a `_sw-precache=[hash]` to the end of each URL when populating, updating its cache and even fetching these subresouces. Those `_sw-precache=[hash]` are what we called **cache-busting parameter\\***. It can prevent service worker from responding and caching out-of-date responses found in browsers’ HTTP cache indefinitely.\n\nBecause each build would re-calculate hashes and re-generate a new `sw.js` with new `precacheConfig` containing those new hashes, `sw.js` can now determine the version of each subresources thus decide what part of its cache needs a update. **This is pretty similar with what we commonly do when realizing long-term caching with webpack or gulp-rev, to do a byte-diff ahead of runtime.**\n\n\\*: Developer can opt out this behaviour with `dontCacheBustUrlsMatching` option if they set HTTP caching headers right. More details on [Jake’s Post](https://jakearchibald.com/2016/caching-best-practices/).\n\n## On Install\n\n> ServiceWorker gives you an install event. You can use this to get stuff ready, stuff that must be ready before you handle other events.\n\nDuring the `install` lifecycle, `sw.js` open the cache and get started to populate its cache. One cool thing that it does for you is its **incremental update** mechanism.\n\nSw-precache would search each cache key (the values of `urlsToCacheKeys`) in the `cachedUrls`, a ES6 Set containing URLs of all requests indexed from current version of cache, and only `fetch` and `cache.put` resources couldn’t be found in cache, i.e, never be cached before, thus reuse cached resources as much as possible.\n\nIf you can not fully understand it, don’t worry. We will recap it later, now let’s move on.\n\n## On Activate\n\n> Once a new ServiceWorker has installed & a previous version isn’t being used, the new one activates, and you get an `activate` event. Because the old version is out of the way, it's a good time to handle schema migrations in IndexedDB and also delete unused caches.\n\nDuring activation phase, `sw.js` would compare all existing requests in the cache, named `existingRequests` (noticed that it now contains resources just cached on installation phase) with `setOfExpectedUrls`, a ES6 Set from the values of `urlsToCacheKeys`. And delete any requests not matching from cache.\n\n```js\n// sw.js\nexistingRequests.map(function(existingRequest) {\n  if (!setOfExpectedUrls.has(existingRequest.url)) {\n    return cache.delete(existingRequest);\n  }\n})\n```\n\n## On Fetch\n\nAlthough the comments in source code have elaborated everything well, I wanna highlight some points during the request intercepting duration.\n\n### Should Respond?\n\nFirstly, we need to determine whether this request was included in our “pre-caching list”. If it was, this request should have been pre-fetched and pre-cached thus we can respond it directly from cache.\n\n```js\n// sw.js*  \nvar url = event.request.url      \nshouldRespond = urlsToCacheKeys.has(url);\n```\n\nNoticed that we are matching raw URLs (e.g. `http://localhost/js/a.js`) instead of the hashed ones. It prevent us from calculating hashes at runtime, which would have a significant cost. And since we have kept the relationship in `urlToCacheKeys` it’s easy to index the hashed one out.\n\n_\\* In real cases, sw-precache would take `ignoreUrlParametersMatching` and `directoryIndex` options into consideration._\n\n### Navigation Fallback\n\nOne interesting feature that sw-precache provided is `navigationFallback`(previously `defaultRoute`), which detect navigation request and respond a preset fallback HTML document when the URL of navigation request did not exist in `urlsToCacheKeys`.\n\nIt is presented for SPA using History API based routing, allowing responding arbitrary URLs with one single HTML entry defined in `navigationFallback`, kinda reimplementing a Nginx rewrite in service worker\\*. Do noticed that service worker only intercept document (navigation request) inside its scope (and any resources referenced in those documents of course). So navigation towards outside scope would not be effected.\n\n_\\* `navigateFallbackWhitelist` can be provided to limit the “rewrite” scope._\n\n### Respond from Cache\n\nFinally, we get the appropriate cache key (the hashed URL) by raw URL with `urlsToCacheKeys` and invoke `event.respondWith()` to respond requests from cache directly. Done!\n\n```js\n// sw.js*\nevent.respondWith(\n  caches.open(cacheName).then(cache => {\n    return cache.match(urlsToCacheKeys.get(url))\n      .then(response => {\n        if (response) return response;\n      });\n  })\n);\n```\n\n_\\* The code was “ES6-fied” with error handling part removed._\n\n## Cache Management Recap\n\nThat’s recap the cache management part with a full lifecycle simulation.\n\n### The first build\n\nSupposed we are in the very first load, the `cachedUrls` would be a empty set thus all subresources listed to be pre-cached would be fetched and put into cache on SW install time.\n\n```js\n// cachedUrls  \nSet(0) {}\n\n// urlToCacheKeys  \nMap(2) {  \n  \"http.../js/a.js\" => \"http.../js/a.js?_sw-precache=3cb4f0\",   \n  \"http.../css/b.js\" => \"http.../css/b.css?_sw-precache=c5a951\"  \n}\n\n// SW Network Logs  \n[sw] GET a.js?_sw-precache=3cb4f0      \n[sw] GET b.css?_sw-precache=c5a951\n```\n\nAfter that, it will start to control the page immediately because the `sw.js` would call `clients.claim()` by default. It means the `sw.js` will start to intercept and try to serve future fetches from caches, so it’s good for performance.\n\nIn the second load, all subresouces have been cached and will be served directly from cache. So none requests are sent from `sw.js`.\n\n```js\n// cachedUrls  \nSet(2) {  \n  \"http.../js/a.js? _sw-precache=3cb4f0\",   \n  \"http.../css/b.css? _sw-precache=c5a951\"  \n}\n\n// urlToCacheKeys  \nMap(2) {  \n  \"http.../js/a.js\" => \"http.../js/a.js? _sw-precache=3cb4f0\",   \n  \"http.../css/b.js\" => \"http.../css/b.css? _sw-precache=c5a951\"  \n}\n\n// SW Network Logs  \n// Empty\n```\n\n### The second build\n\nOnce we create a byte-diff of our subresouces (e.g., we modify `a.js` to a new version with hash value `d6420f`) and re-run the build process, a new version of `sw.js` would be also generated.\n\nThe new `sw.js` would run alongside with the existing one, and start its own installation phase.\n\n```js\n// cachedUrls  \nSet(2) {  \n  \"http.../js/a.js? _sw-precache=3cb4f0\",   \n  \"http.../css/b.css? _sw-precache=c5a951\"  \n}\n\n// urlToCacheKeys  \nMap(2) {  \n  \"http.../js/a.js\" => \"http.../js/a.js? _sw-precache=d6420f\",   \n  \"http.../css/b.js\" => \"http.../css/b.css? _sw-precache=c5a951\"  \n}\n\n// SW Network Logs  \n [sw] GET a.js?_sw-precache=d6420f\n```\n\nThis time, `sw.js` see that there is a new version of `a.js` requested, so it fetch `/js/a.js?_sw-precache=d6420f`  and put the response into cache. In fact, we have two versions of `a.js` in cache at the same time in this moment.\n\n```js\n// what's in cache?\nhttp.../js/a.js?_sw-precache=3cb4f0\nhttp.../js/a.js?_sw-precache=d6420f\nhttp.../css/b.css?_sw-precache=c5a951\n```\n\nBy default, `sw.js` generated by sw-precache would call `self.skipWaiting` so it would take over the page and move onto activating phase immediately.\n\n```js\n// existingRequests\nhttp.../js/a.js?_sw-precache=3cb4f0\nhttp.../js/a.js?_sw-precache=d6420f\nhttp.../css/b.css?_sw-precache=c5a951\n\n// setOfExpectedUrls\nSet(2) {\n  \"http.../js/a.js?_sw-precache=d6420f\", \n  \"http.../css/b.css?_sw-precache=c5a951\"\n}\n\n// the one deleted\nhttp.../js/a.js?_sw-precache=3cb4f0\n```\n\nBy comparing existing requests in the cache with set of expected ones, the old version of `a.js` would be deleted from cache. This ensure there is only one version of our site’s resources each time.\n\nThat’s it! We finish the simulation successfully.\n\n## Conclusions\n\nAs its name implied, sw-precache is designed specifically for the needs of precaching some critical static resources. It only does one thing but does it well. I’d love to give you some opinionated suggestions but you decide whether your requirements suit it or not.\n\n### Precaching is NOT free\n\nSo don’t precached everything. Sw-precache use a [“On Install — as a dependency”](https://jakearchibald.com/2014/offline-cookbook/#on-install-as-a-dependency) strategy for your precache configs. A huge list of requests would delay the time service worker finishing installing and, in addition, wastes users’ bandwidth and disk space.\n\nFor instance, if you wanna build a offline-capable blogs. You had better not include things like `'posts/*.html` in `staticFileGlobs`. It would be a huge disaster to data-sensitive people if you have hundreds of posts. Use a Runtime Caching instead.\n\n### “App Shell”\n\n> A helpful analogy is to think of your App Shell as the code and resources that would be published to an app store for a native iOS or Android application.\n\nThough I always consider that the term “App Shell” is too narrow to cover its actual usages now, It is widely used and commonly known. I personally prefer calling them **“Web Installation Package”** straightforward because they can be truly installed into users’ disks and our web app can boot up directly from them in any network environments. The only difference between “Web Installation Package” and iOS/Android App is that we need strive to limit it within a reasonable size.\n\nPrecaching is perfect for this kinda resources such as entry html, visual placeholders, offline pages etc., because they can be static in one version, small-sized, and most importantly, part of critical rendering path. We wanna put first meaningful paint ASAP to our user thus we precache them to eliminate HTTP roundtrip time.\n\nBTW, if you are using HTML5 Application Cache before, sw-precache is really a perfect replacement because it can cover nearly all use cases the App Cache provide.\n\n### This is not the end\n\nSw-precache is just one of awesome tools that can help you build service worker. If you are planing to add some service worker power into your website, Don’t hesitate to checkout sw-toolbox, sw-helper (a new tool Google is working on) and many more from communities.\n\nThat’s all. Wish you enjoy!\n"
  },
  {
    "path": "_posts/2017-06-25-you-are-slaves.markdown",
    "content": "---\nlayout:     post\ntitle:      \"他是狗，你们便是苟奴隶\"\ndate:       2017-06-24 12:00:00\nauthor:     \"Hux\"\nheader-style: text\ncatalog: false\npublished: false\ntags:\n  - 被夹\n---\n\n> 在知乎被删帖，我理解知乎。\n> 你说你们做不了什么，我也理解你们。\n> <br/>\n> 只是，总要有人，还敢说点反对的声音吧？\n> 只是，不想让这一切，看起来都变得如此理所应当吧？\n> <br/>\n> 你说，你们也抗争了\n> 那就站出来，让我们相信，你们还在吧？\n\n我甚至都不需要写出「刘国梁」这三个顶天立地的大字，你们便知道我今天要说什么了。\n骂狗官、骂体制、骂 D，骂的人已经够多了。我故是可以再骂，却也深知自己甚至连让他们听到这份声音的能力都没有。\n\n**但今天，我要骂的是你们，至少还能听到我声音的你们。** 我亲爱的同行们啊，那些在微博、知乎与其他社交网络公司工作的你们啊。无论你是我推心置腹的好友，相识或共事过的伙伴，还是素未谋面的陌生人，对不起，今天我要骂的就是你们。\n\n微博，「随时随地发现新鲜事」，可这个这世界却只能发生你审核过的新鲜事。\n\n知乎，「发现更大的世界」，可我们却只能发现你审核过的世界。\n\n好一个又一个讲着漂亮故事的互联网公司啊，你们不是打着 UGC、言论开放的旗号、沾着民智渐开，民主自由的福利吗？好一个又一个独立自强、新时代的互联网员工啊，你们不是为「建设了中国互联网」，打造了一个「用户喜爱的产品」而感到自豪吗？\n\n嘴上说着不要，身体却已经跪在金钱与权势之下任人驱使了嘛。\n\n**这种情况，我们一般称之为「奴隶」。**\n\n> 奴隶，通常指失去人身自由并被他人任意驱使的，为他们做事的人。\n\n---\n\n你可以跟我喊冤，说你也想反抗过，说你也很无奈。说你也思考过政府和媒体的关系，说你也知道权利、体制的可怖与强大，说你不能承担反抗的后果。\n\n可难道那些国乒远动员们不知道吗？从小成长在体制内的他们，比你清楚得多太多了。可是为了自己的权利、自己的公平，自己的爱，他们还是集体站出来了啊！他们直面着比你大得多的体制压力，承受着可能影响他们一生的严重后果。你告诉他们应该隐忍？识大体？沉默？那叫做苟且，叫做冷血，叫做向暴政与不公屈服！他们也知道，在体制面前他们势单力薄，可能是以卵击石头。可是他们仍然不顾一切的发声，那是在请求我们的帮助啊！\n\n而你现在却还在问我你为什么要怪罪我，而不去怪罪那些「上面」的人？\n\n我当然也怪罪「上面」的人！但是你们，是你们！直接挡住了他们的求救，挡住了人民的援助，挡住了人民发声的渠道啊！传统媒体是 D 的喉舌，而你们呢，为自由奋斗的你们呢？你们本该成为人民的耳朵、眼睛和嘴啊，现在却愿意让人民都成为聋子、瞎子、哑巴了吗？\n\n你们或许觉得一己之力无法改变任何事情，于是沉默，每个人都沉默，仿佛罪恶都被平摊了，到每个人身上就都接近于 0 了。仿佛这一切就都理所应当了，可是真的就理所应当了吗？\n\n**一群人在作恶时，每个个体就不是在作恶了吗！？**\n\n**天再黑也要说话啊。**\n\n<br />\n\n\n我是一个胖球迷，从小就是。初中、高中、大学一路在校队打着酱油，参加一些业余的小比赛。很多人说在中国会打点胖球没什么稀奇，这是国球。很多人说打胖球不帅，女生们都围着打篮球的转。可是没办法，我就是喜欢，床头贴着 LGL 带着二王一马拿下世乒赛的海报，家里的《乒乓世界》一垛又一垛，一直到现在也不舍得扔。\n\n我是一个程序员，从小就是。在几家公司打过酱油，做过一些小分享。很多人说程序员都是农民，天天干一些重复的事情，加班多，死得早。可是没办法，我就是喜欢，喜欢互联网这个崇尚自由与平等的地方，欣赏那些用互联网让世界变得更加美好的人们。我不是为了谋生而选择了这个职业，我是为了自由与骄傲。\n\n> We will not go quietly into the night!\n> We will not vanish without a fight!\n> We're going to live on!\n> We're going to survive!\n> Today, we celebrate our Independence Day!\n\n**国乒，愿有属于你们的独立日。**\n"
  },
  {
    "path": "_posts/2017-07-12-upgrading-eleme-to-pwa.markdown",
    "content": "---\nlayout:       post\ntitle:        \"饿了么的 PWA 升级实践\"\nsubtitle:     \"Upgrading Ele.me to Progressive Web App\"\ndate:         2017-07-12 12:00:00\nauthor:       \"Hux\"\nheader-img:   \"img/in-post/post-eleme-pwa/eleme-at-io.jpg\"\nheader-mask:  0.3\ncatalog:      true\nmultilingual: true\ntags:\n    - Web\n    - PWA\n---\n\n<!-- Chinese Version -->\n<div class=\"zh post-container\">\n    {% capture about_zh %}{% include posts/2017-07-12-upgrading-eleme-to-pwa/zh.md %}{% endcapture %}\n    {{ about_zh | markdownify }}\n</div>\n\n<!-- English Version -->\n<div class=\"en post-container\">\n    {% capture about_en %}{% include posts/2017-07-12-upgrading-eleme-to-pwa/en.md %}{% endcapture %}\n    {{ about_en | markdownify }}\n</div>\n"
  },
  {
    "path": "_posts/2017-07-26-farewell-flash.md",
    "content": "---\nlayout: post\ntitle: \"Farewell, Flash. 感谢你，但这一次是真正的永别。\"\nsubtitle: \"So long, and thanks for all the Flash\"\nauthor: \"Hux\"\nheader-img: \"img/post-bg-farewell-flash.jpg\"\nheader-mask: 0.2\ntags:\n  - Web\n  - Flash\n---\n\n> 本文首发于我的知乎专栏 [The Little Programmer](https://zhuanlan.zhihu.com/p/28109200)，转载请保留链接 ;)\n\n一年半前，我曾和 Flash 作过一次告别。那一次，Adobe Flash Professional CC 被重新命名为了 Adobe Animate CC，宣告着 Flash 作为一个创作工具走到了尽头。\n\n\n![](/img/in-post/post-f-f-weibo.png)\n\n  \n\n而今天，通过 Chromium 博客 [So long, and thanks for all the Flash](https://blog.chromium.org/2017/07/so-long-and-thanks-for-all-flash.html) 我才得知，Adobe 官博在 [Flash & The Future of Interactive Content](https://blogs.adobe.com/conversations/2017/07/adobe-flash-update.html) 一文中，宣布将在 2020 年底时停止发布与更新 Flash Player。这一次，意味着 Flash 作为一个平台走到了尽头。\n\n  \n\n在不少人眼里，Flash 与 HTML5 是纯粹的竞争关系，我们应该为 HTML5 与 Open Web 标准的胜利欢呼，而将 Flash 狠狠的咒骂在黄泉之下。但其实，大多数人都忘记了，或是从不曾知道：**HTML5（严谨的来说，其 marketing 含义中所涵盖的那些 Web APIs），有很大一部分正是 Flash 平台、Flash 社区对 web 标准做出的贡献。**\n\n正如 [Flash & The Future of Interactive Content](https://blogs.adobe.com/conversations/2017/07/adobe-flash-update.html) 所说：\n\n> Adobe has long played a leadership role in advancing interactivity and creative content – from video, to games and more – on the web. Where we’ve seen a need to push content and interactivity forward, we’ve innovated to meet those needs. **Where a format didn’t exist, we invented one – such as with Flash and Shockwave. And over time, as the web evolved, these new formats were adopted by the community, in some cases formed the basis for open standards, and became an essential part of the web.**\n\n当我们（企业、用户）需要 web 平台承载包括视频、游戏在内的各种富交互内容而 web 平台本身还不具备这样的能力时，我们通过给予这个平台一种新的格式，以满足大家的需求，这就是 Flash Player，作为一种私有平台与浏览器插件，却能一度成为 web 事实标准的客观原因。\n\n而时至今日，这些 web 平台所欠缺的能力，在得到市场与社区的认可之后，逐渐被从 Flash 中吸收与扬弃，成为了诸如 HTML5 Video/Audio/Canvas、WebGL 这些真正的 Open Web 标准。这时候，这些在诞生之初颇为创新的，作为了一种「过渡手段」、「Shim」的私有平台，便自然而然的，慢慢的不再被需要了。\n\n**这并不应该理解为一种失败，而应该说，它们「功成身退」了。**\n\n<br/>\n\nActionScript 3.0，Flash 中的御用编程语言，作为 ES4 的唯一实现，[推动了 ECMAScript 标准的发展，深远得影响着现代 JavaScript](https://www.zhihu.com/question/49170215/answer/114640341)；\n\nAdobe Flex，Flash 平台的企业开发框架，在今年和 [@徐飞](https://www.zhihu.com/people/sharpmaster) 老师聊到时，还一起怀念并认可其相比现代 web 前端/客户端开发在工具链、协作、兼容性、UI 组件等方面的先进与成熟；\nAdobe AIR，作为最早借鉴 JRT 将 web 相关技术的 Runtime 植入操作系统或捆绑在可执行文件内的跨平台开发方案，或许可以视作 Cordova、Electron、NodeWebkit、ReactNative 这些方案的一个前身与成功先例；\n\nMicrosoft IE 私有技术 ActiveX 中的 XMLHTTP，作为 XMLHTTPRequest 的前身，促进了 Ajax 的诞生与 Web 2.0 时代的来临；\n\nGoogle Gears 作为 2008 年时为了增强 web 应用的浏览器插件，其私有 API 分别是 App Cache、Web Worker、WebSQL 等标准或标准未遂的前身；\n\nCordova/Phonegap 作为第一个面向移动端的 Hybrid 方案，成为了 web 开发与移动设备的 polyfill 与桥梁，加速了 Web 平台 Device APIs 的发展，并与 WebOS、FirefoxOS、Chrome Apps、Windows Runtime Apps 等一同影响了 Progressive Web App 的出现；\n\nGoogle Extension 中 Background Page 与 Event Page 多年对 web 平台后台持续计算的尝试，直接帮助了 Service Worker 的 API 设计；\n\nGoogle 的 NativeClient、Mozilla 的 asm.js 对于 web 追逐 native 性能的极致追求，则奠定了 Web Assembly 的诞生……\n\n你看，在这条道路上，Flash 与它的朋友们，其实并不孤单。\n\n**「看到你长大了，我也就可以心满意足的离开了。」**\n\n**就像是， web 技术发展的必然规律一样，**\n\n**而 Open Web 则因此不朽。**\n\n<br/>\n\n我很高兴，Google Chrome、Mozilla Firefox、Microsoft Edge 都能这么写到：\n\n> Flash helped make the web a rich, dynamic experience, and **shaped the modern set of web standards.**  \n>   \n> --- \"[So long, and thanks for all the Flash](https://blog.chromium.org/2017/07/so-long-and-thanks-for-all-flash.html)\" Chromium Blog\n\n  \n\n> Over the years, Flash has helped bring the Web to greatness with innovations in media and animation, **which ultimately have been added to the core web platform.**  \n>   \n> --- \"[Firefox Roadmap for Flash End-of-Life](https://blog.mozilla.org/futurereleases/2017/07/25/firefox-roadmap-flash-end-life/)\" Mozilla Blog\n\n  \n\n> Flash led the way on the web for rich content, gaming, animations, and media of all kinds, and **inspired many of the current web standards powering HTML5.**  \n>   \n> --- \"[The End of an Era – Next Steps for Adobe Flash](https://blogs.windows.com/msedgedev/2017/07/25/flash-on-windows-timeline/)\" Windows Blog\n\n  \n\n感谢你，Flash。\n\n感谢你们，那些「功成身退」的你们。"
  },
  {
    "path": "_posts/2017-10-06-css-complaints.md",
    "content": "---\nlayout: post\ntitle: \"为什么 CSS 这么难学？\"\nsubtitle: \"Why I dislike CSS as a programming language\"\nauthor: \"Hux\"\nheader-img: \"img/post-bg-css.jpg\"\nheader-img-credit: \"@WebdesignerDepot\"\nheader-img-credit-href: \"medium.com/@WebdesignerDepot/poll-should-css-become-more-like-a-programming-language-c74eb26a4270\"\nheader-mask: 0.4\ntags:\n  - Web\n  - CSS\n  - 知乎\n---\n\n> 这篇文章转载自[我在知乎上的回答](https://www.zhihu.com/question/66167982/answer/240434582)\n\n对我来说，CSS 难学以及烦人是因为它**「出乎我意料之外的复杂」**且让我觉得**「定位矛盾」**。\n\n[@方应杭](//www.zhihu.com/people/b90c7eb6d3d5a4e2ce453dd8ad377672) 老师的答案我赞了：CSS 的属性互不正交，大量的依赖与耦合难以记忆。\n\n[@顾轶灵](//www.zhihu.com/people/596c0a5fdd9b36cea06bac348d418824) [@王成](//www.zhihu.com/people/c02ec74a44ee4a6784d002c33e293652) 说得也没错：CSS 的很多规则是贯彻整个体系的，而且都记在规范里了，是有规律的，你应该好好读文档而不是去瞎试。\n\n\n「**CSS是一门正儿八经的编程语言，请拿出你学C++或者Java的态度对待它**」\n\n但是问题就在这了，无论从我刚学习前端还是到现在，我都没有把 CSS 作为一门正儿八经的编程语言（**而且显然图灵不完全的它也不是**），CSS 在我眼里一直就是一个布局、定义视觉样式用的 DSL，与 HTML 一样就是一个标记语言。\n\n写 CSS 很有趣，CSS 中像继承、类、伪类这样的设计确实非常迎合程序员的思路，各种排列组合带来了很多表达上的灵活性。但如果可以选择，在生产环境里我更愿意像 iOS/Android/Windows 开发那样，把这门 DSL 作为 IDE WYSIWYG 编辑器的编译目标就可以了，当然你可以直接编辑生成的代码，但我希望「对于同一种效果，有比较确定的 CSS 表达方式」\n\n因为我并不在 CSS 里处理数据结构，写算法、业务逻辑啊，我就是希望我能很精确得表达我想要的视觉效果就可以了。如果我需要更复杂的灵活性和控制，你可以用真正的编程语言来给我暴露 API，而不是在 CSS 里给我更多的「表达能力」\n\n\n**CSS 语言本身的表达能力对于布局 DSL 来说是过剩的**，所以你仅仅用 CSS 的一个很小的子集就可以在 React Native 里搞定 iOS/Android 的布局了。你会发现各个社区（典型如 React）、团队都要花很多时间去找自己项目适合的那个 CSS 子集（so called 最佳实践）。而且 CSS 的这种复杂度其实还挺严重得影响了浏览器的渲染性能，很多优化变得很难做。\n\n**而 CSS 的表达能力对于编程语言来说又严重不够**，一是语言特性不够，所以社区才会青睐 Less、Sass 这些编译到 CSS 的语言，然后 CSS 自己也在加不痛不痒的 Variable。二是 API 不够，就算你把规范读了，你会发现底层 CSSOM 的 Layout、Rendering 的东西你都只能强行用声明式的方式去 hack（比如用 transform 开新的 composition layer）而没有真正的 API 可以用，所以 W3C 才会去搞 Houdini 出来。\n\n这种不上不下的感觉就让我觉得很「矛盾」，你既没法把 CSS 当一个很简单的布局标记语言去使用，又没办法把它作为一个像样的编程语言去学习和使用。\n\n\n在写 CSS 和 debug CSS 的时候我经常处在一种「MD 就这样吧反正下次还要改」和「MD 这里凭什么是这样的我要研究下」的精分状态，可是明明我写 CSS 最有成就感的时候是看到漂亮的 UI 啊。\n\n以上。\n"
  },
  {
    "path": "_posts/2017-12-12-halting-problem.md",
    "content": "---\nlayout: post\ntitle: \"如何通俗地解释停机问题？\"\nsubtitle: \"How to explain the Halting Problem?\"\nauthor: \"Hux\"\nheader-img: \"img/post-bg-halting.jpg\"\nheader-mask: 0.3\ntags:\n  - 知乎\n  - 计算理论\n---\n\n> 这篇文章转载自[我在知乎上的回答]( https://www.zhihu.com/question/20081359/answer/275107187)\n\n我用 Python 伪代码来解释下，我觉得对这个问题有兴趣的应该都是有点编程基础的，所以直接上 code 应该是最容易的。\n\n## 背景知识\n\n「停机问题」研究的是：是否存在一个「程序」，能够判断另外一个「程序」在特定的「输入」下，是会给出结果（停机），还是会无限执行下去（不停机）。\n\n在下文中，我们用「函数」来表示「程序」，「函数返回」即表示给出了结果。\n\n## 正文\n\n我们假设存在这么一个「停机程序」，不管它是怎么实现的，但是它能够回答「停机问题」：它接受一个「程序」和一个「输入」，然后判断这个「程序」在这个「输入」下是否能给出结果：\n\n```py\ndef is_halt(program, input) -> bool:\n  # 返回 True  如果 program(input) 会返回\n  # 返回 False 如果 program(input) 不返回\n```\n\n（在这里，我们通过把一个函数作为另一个函数的输入来描述一个「程序」作为另一个「程序」的「输入」，如果你不熟悉「头等函数」的概念，你可以把所有文中的函数对应为一个具备该函数的对象。）\n\n为了帮助大家理解这个「停机程序」的功能，我们举个使用它的例子：\n\n```py\nfrom halt import is_halt\n\ndef loop():\n  while(True):\n      pass\n\n# 如果输入是 0，返回，否则无限循环\ndef foo(input):\n  if input == 0:\n    return\n  else:\n    loop()\n\nis_halt(foo, 0)  # 返回 True\nis_halt(foo, 1)  # 返回 False\n```\n\n是不是很棒？\n\n不过，如果这个「停机程序」真的存在，那么我就可以写出这么一个「hack 程序」：\n\n```py\nfrom halt import is_halt\n\ndef loop():\n  while(True):\n      pass\n\ndef hack(program):\n  if is_halt(program, program):\n    loop()\n  else:\n    return\n```\n\n这个程序说，如果你 `is_halt` 对 `program(program)` 判停了，我就无限循环；如果你判它不停，我就立刻返回。\n\n那么，如果我们把「hack 程序」同时当做「程序」和「输入」喂给「停机程序」，会怎么样呢？\n\n```py\nis_halt(hack, hack)\n```\n\n你会发现，如果「停机程序」认为 `hack(hack)` 会给出结果，即 `is_halt(hack, hack)`) 返回 `True`) ，那么实际上 `hack(hack)` 会进入无限循环。而如果「停机程序」认为 `hack(hack)` 不会给出结果，即 `is_halt(hack, hack)` 返回 ，那么实际上 `hack(hack)` 会立刻返回结果……\n\n这里就出现了矛盾和悖论，所以我们只能认为，我们最开始的假设是错的：\n\n**这个「停机程序」是不存在的。**\n\n## 意义\n\n简单的说，「停机问题」说明了现代计算机并不是无所不能的。\n\n上面的例子看上去是刻意使用「自我指涉」来进行反证的，但这只是为了证明方便。实际上，现实中与「停机问题」一样是现代计算机「不可解」的问题还有很多，比如所有「判断一个程序是否会在某输入下怎么样？」的算法、Hilbert 第十问题等等，wikipedia 甚至有一个 [List of undecidable problems](https://link.zhihu.com/?target=https%3A//en.wikipedia.org/wiki/List_of_undecidable_problems)。\n\n## 漫谈\n\n如果你觉得只是看懂了这个反证法没什么意思：\n\n1.  最初图灵提出「停机问题」只是针对「图灵机」本身的，但是其意义可以被推广到所有「算法」、「程序」、「现代计算机」甚至是「量子计算机」。\n2.  实际上「图灵机」只能接受（纸带上的）字符串，所以在图灵机编程中，无论是「输入」还是另一个「图灵机」，都是通过编码来表示的。\n3.  「图灵机的计算能力和现代计算机是等价的」，更严谨一些，由于图灵机作为一个假象的计算模型，其储存空间是无限大的，而真实计算机则有硬件限制，所以我们只能说「不存在比图灵机计算能力更强的真实计算机」。\n4.  这里的「计算能力」（power）指的是「能够计算怎样的问题」（capablity）而非「计算效率」（efficiency），比如我们说「上下文无关文法」比「正则表达式」的「计算能力」强因为它能解决更多的计算问题。\n5.  「图灵机」作为一种计算模型形式化了「什么是算法」这个问题（[邱奇－图灵论题](https://link.zhihu.com/?target=https%3A//en.wikipedia.org/wiki/Church%25E2%2580%2593Turing_thesis)）。但图灵机并不是唯一的计算模型，其他计算模型包括「[Lambda 算子](https://link.zhihu.com/?target=https%3A//en.wikipedia.org/wiki/Lambda_calculus)」、[μ-递归函数](https://link.zhihu.com/?target=https%3A//en.wikipedia.org/wiki/%25CE%259C-recursive_function)」等，它们在计算能力上都是与「图灵机」等价的。因此，我们可以用「图灵机」来证明「[可计算函数](https://link.zhihu.com/?target=https%3A//en.wikipedia.org/wiki/Computable_function)」的上界。也因此可以证明哪些计算问题是超出上界的（即不可解的）。\n6.  需要知道的是，只有「可计算的」才叫做「算法」。\n7.  「停机问题」响应了「哥德尔的不完备性定理」。\n\n## 拓展阅读：\n\n中文：\n\n- [Matrix67: 不可解问题(Undecidable Decision Problem)](https://link.zhihu.com/?target=http%3A//www.matrix67.com/blog/archives/55)\n\n- [Matrix67: 停机问题、Chaitin 常数与万能证明方法](https://link.zhihu.com/?target=http%3A//www.matrix67.com/blog/archives/901)\n\n- [刘未鹏：康托尔、哥德尔、图灵--永恒的金色对角线(rev#2) - CSDN 博客](https://link.zhihu.com/?target=http%3A//blog.csdn.net/pongba/article/details/1336028)\n\n- [卢昌海：Hilbert 第十问题漫谈 (上)](https://link.zhihu.com/?target=http%3A//www.changhai.org/articles/science/mathematics/hilbert10/1.php)\n\n英文：\n\n- [《Introduction to the Theory of Computation》](https://link.zhihu.com/?target=https%3A//en.wikipedia.org/wiki/Introduction_to_the_Theory_of_Computation)\n\n- [Turing Machines Explained - Computerphile](https://link.zhihu.com/?target=https%3A//www.youtube.com/watch%3Fv%3DdNRDvLACg5Q)\n\n- [Turing & The Halting Problem - Computerphile](https://link.zhihu.com/?target=https%3A//www.youtube.com/watch%3Fv%3DmacM_MtS_w4%26t%3D29s)\n\n- [Why, really, is the Halting Problem so important?](https://link.zhihu.com/?target=https%3A//cs.stackexchange.com/questions/32845/why-really-is-the-halting-problem-so-important)\n"
  },
  {
    "path": "_posts/2017-12-12-uncomputable-funcs.md",
    "content": "---\nlayout: post\ntitle: \"如何证明不可计算的函数比可计算的函数多？\"\nsubtitle: \"Why is there more uncomputable functions?\"\nauthor: \"Hux\"\nheader-img: \"img/post-bg-infinity.jpg\"\nheader-mask: 0.3\nmathjax: true\ntags:\n  - 知乎\n  - 计算理论\n---\n\n> 这篇文章转载自[我在知乎上的回答](https://www.zhihu.com/question/51508063/answer/275401076)\n\n严谨的证明的话，可以使用「形式语言」（[Formal language](https://en.wikipedia.org/wiki/Formal_language)）来证明：\n\n在可计算理论和计算复杂度理论中，每个「计算问题」都被描述为一个一个「形式语言」，即字符串的集合。比如对于判断一个图是否是无向连通图这个问题：我们可以写为一个描述所有无向连通图的集合：\n\n$$\nA = \\{ \\langle G \\rangle \\vert G \\text{ is a connected undirected graph}\\}\n$$\n\n由于图灵机只能接受字符串，所以这里的尖括号表示对图的「编码」。出于简单，我们全部使用现实计算机所使用的字母表\n$\\Sigma = \\\\{0, 1\\\\}$，所以「编码」即一个对象的二进制字符串描述。\n\n如果我们能构造出一个图灵机来「决定」这个「形式语言」，即可以判断一个「输入」是否属于这个集合（membership 与 non-membership），那么我们可以说我们用「图灵机」描述了一个「算法」来计算这个问题，而这个「计算问题」所对应的函数是「可计算的」，否则是「不可计算的」。（注 1）\n\n那么，如果我们有一个包含了所有「可计算函数」的集合，这个集合会有多大呢？\n\n<br>\n\n由于\n\n- 所有「可计算函数」总有一个对应的「图灵机」来计算它\n- 每一个「图灵机」都可以被「编码」为一个不同的 0、1 序列，比如 000，010...\n- 0、1 序列、即二进制，总是可以被转换为一个十进制数的\n\n所以，我们这个集合实际上是与整数集 $Z$ 一样大（等势）的，我们把这个集合表示为 $\\Sigma^{\\*}$。 易知 $Z$ 是「无穷可数（countably infinite）」的，所以我们有无穷可数个「可计算函数」（注 2）。\n\n<br>\n\n而「计算问题」有多少个呢？\n\n这个问题可以等同于，我们有多少个形如 $\\\\{000, 010\\\\}$ 这样的 0，1 序列的集合？即 $\\Sigma^{\\*}$ 这个集合有多少个子集？用数学语言描述就是求 $\\Sigma^{\\*}$ 的幂集的势 $\\| P(\\Sigma^{\\*})\\|$ 。\n\n由于 $\\Sigma^{\\*}$ 与 $Z$ 是等势的，所以这个问题等价于求 $\\|P(Z)\\|$ 的大小。根据 [Cantor's theorem](https://en.wikipedia.org/wiki/Cantor%2527s_theorem)，一个「无穷可数」的集合的幂集是「无穷不可数（uncountably infinite）」的。（注 3）\n\n<br>\n\n根据 [Cantor's theorem](https://en.wikipedia.org/wiki/Cantor%2527s_theorem)，「无穷不可数集」要比「无穷可数集」大。\n\n同时，「无穷不可数集」减去「无穷可数集」后仍然是「无穷不可数集」。（注 4）\n\n所以，「不可计算函数集」，即「计算问题集」与「可计算函数集」的差，仍是「无穷不可数集」，仍比是为「无穷可数集」的「可计算函数集」大。\n\n因此，「不可计算的函数」比「可计算的函数」多。\n\n证毕。\n\n<br>\n\n注：\n\n1.  「[可计算函数](https://en.wikipedia.org/wiki/Computable_function)」是算法的直觉说法，「[邱奇－图灵论题](https://en.wikipedia.org/wiki/Church%25E2%2580%2593Turing_thesis)」猜想任何在算法上可计算的问题同样可以由图灵机计算。但图灵机并不是唯一的计算模型，其他计算模型包括「[Lambda 算子](https://en.wikipedia.org/wiki/Lambda_calculus)」、「$\\mu$ - [递归函数](https://en.wikipedia.org/wiki/%25CE%259C-recursive_function)」等，它们在计算能力上都是与「图灵机」等价的。\n2.  证明「所有可计算函数」的集合是「无穷可数集」的方式有很多，只要找到任意一个与「自然数集」的「双射」即可\n3.  也可以直接用康托的对角线法（[Cantor's diagonal argument](https://en.wikipedia.org/wiki/Cantor%2527s_diagonal_argument)）证明「所有计算问题」的集合是「无穷不可数集」\n4.  可以用反证法得证\n5.  知乎能用 LaTex 了好评\n6. [Aleph Number - Wikipedia](https://en.wikipedia.org/wiki/Aleph_number)\n"
  },
  {
    "path": "_posts/2018-05-11-pwa-zh-preface.md",
    "content": "---\nlayout:       post\ntitle:        \"《PWA 实战》推荐序\"\ndate:         2018-05-11 12:00:00\nauthor:       \"Hux\"\nheader-style: text\ncatalog:      true\ntags:\n    - Web\n    - PWA\n---\n\n> 「博文视点」邀请我给[《PWA实战：面向下一代的Progressive Web APP》](https://union-click.jd.com/jdc?e=&p=AyIGZRhfEgoaBVUbXBYyEgRXHF8UChI3EUQDS10iXhBeGlcJDBkNXg9JHU4YDk5ER1xOGRNLGEEcVV8BXURFUFdfC0RVU1JRUy1OVxUBEABRGlMVMmZMDxwsR2cWZVdbO2ocFV9cSB5qfnILWStaJQITBlcTWhYLEQJlK1sSMkRpVRpaFAMTAlUeWCUDIgdSG1oXARIPUx5eFAMiAFUSaxYBFAJVElgSCw4FURxTFAoiN2UYayUyEjdWKxl7UEEPUR5dFFYWBgYTXhZXFA5WTlMSVxoDUh9TQFFBAVUrWRQDFg4%3D) 写的推荐序。\n\nProgressive Web App 是继 Ajax、响应式设计、HTML5 之后，web 平台的又一次革命性突破。它在开放 Web 标准的基础之上，突破了以往 Web 应用只能\b「依赖互联网分发」与「依赖浏览器为入口」的两大桎梏，一下子打开了 Web 应用从性能、架构到用户体验上的一系列可能性。\n\n\bPWA 中最引入注目的核心新特性，\bService Worker，实质上是为 Web 应用带来了一种安全而又低功耗的后台处理能力。无论是用于实现离线\b\b Web 应用所需要的缓存读写与网络代理，还是\b用于提升 Web 应用能力的推送通知、后台同步，其实都得益于这种新的并发能力。随着 Edge 与 Safari\b 的相继发布\b，\b\bService Worker 已经历史性的达到了全浏览器的支持。\n\n而这就要归功于 Web 开放性的力量。相比于其他众多私有的“类 Web”技术，\bPWA 技术完全属于开放 Web 标准。PWA 因此\b具备了\b独一无二的跨平台能力，不止于移动端，Chrome 与 Windows 已经让 PWA 在桌面端也晋升为了第一公民。这使得「一套代码，发布可以同时跨移动桌面设备、跨操作系统、跨浏览器的超级应用」\b真正\b\b成为可能。这里有非常大的想象空间，非常值得我们期待。\n\nPWA 作为“下一代 Web 应用模型”从 2015 年第一次发布，到现在的 2018 年中，国际上 \bGoogle、Twitter、Facebook、Instagram、FlipKart、Uber、Lyft、Pinterest、Tinder、Flipboard、Spotify……国内诸如 AliExpress、饿了么、微博，都已经在使用 PWA 技术甚至发布了专门的 PWA 产品。可以说 PWA 从生态到工具链都已经逐渐成熟，接下来将会迎来更大的爆发。\n\n在这个\b时间点上，很高兴能看到本书的翻译团队能在如此短的时间里\b将最新的技术带回中文社区，非常难能可贵。\b本人也做过一些 PWA 的分享，但要对社区带来更大的推动，我们更需要这样\b完整的大部头作品。\n\n本书原著非常详实且不失生动地涵盖了 PWA 的方方面面。作者不但\u001c通过一个贯穿全书的案例将 PWA 的各项技术串起，还把它们所要解决的问题与可以带来的产品价值也一一娓娓道来。书中讨论到的策略与模式非常实用，\b既可以帮助你快速上手 PWA，也能帮助你对 Web \b应用的工程化有更好的理解。\n\n在此，我谨作为整个中文 Web 社区的一员，感谢团队的贡献！\n"
  },
  {
    "path": "_posts/2018-06-30-dreamer.md",
    "content": "---\nlayout: post\ntitle: \"程序员中的梦想家\"\nsubtitle: \"Dreamers among programmers\"\nauthor: \"Hux\"\nheader-img: \"img/post-bg-dreamer.jpg\"\nheader-mask: 0.4\ntags:\n  - Facebook\n  - Meta\n---\n\n> 本文首发于我的知乎专栏 [The Little Programmer](https://zhuanlan.zhihu.com/p/38722466)，转载请保留链接 ;)\n\n有一类程序员是 visionary 型的，为了实现一些超前的 idea，绕过某些技术的限制，他们写的 code 晦涩高深得只有他们自己能懂，做出来的 tool 看上去很美好结果处处是坑出了 bug 根本没法查，但正是这类人不断创造出新的东西，在洗礼之后成为一个个 big thing。\n\n我每周都要被 infra 的坑 block 得无法工作几次搞得非常沮丧，后来我发现这个锅除了要扔给 FB 外，还有一大半要扔给我周围这群 visionary 的同事们，我工作直接需要接触到的区区五六个人，发起/创造了 Infer, React, Reason, ReasonReact, BuckleScript...\n\n所以这大概就是见证/参与这些 idea 成长的代价吧，也意识到这些东西不是在刚开始就像后来大家接受流行时那么美好的。React 发布 5 周年生日时回放 Jordan/Tom 2013 年第一次对外发布 React/JSX 的视频。我问 Jordan 说你后来怎么没再去分享了。他说你不知道我那天讲完下来被所有听众指着批评。React 第一次在内部使用是 2011 年在 news feed，然后是 2012 年 instagram (pete hunt)，所以这个时间其实很长很长。\n\n很多人（包括我）都会经常觉得 XYZ 新事物跟老东西比太新、太不成熟、体验太不好、想要解决的问题太多、解决方案太 overkill、然后就没有然后了，但其实说不定你在看的这个就是 next big thing 呢。这些梦想家们 vision 里的 big picture 太大了，有的人可能在半个 picture 出来的时候就可以看出来了，有的人则可能要等到整个 picture 都快填满了才看得出来。\n\n如果不是因为 Ads/Messenger 的坑深 React/Reason/Flux 也就不会在这里诞生了，\n\n如果不是因为 Facebook 的坑深 GraphQL/Infer/Hack/Flow/Buck 也就不会在这里诞生了。\n\n正是有一群开垦者不怕坑深才使得各种 idea 成为了大家手上好用的 tool 啊。\n\n梦想家程序员们的工作价值于实干主义的程序员，总是很容易在过程中被低估、忽视，或是得不到尊重。而又在流行之后被神化，仿佛是那个人早已洞察一切一样。其实梦想家的工作，也是一点点累加，一点点迭代起来的。他们也需要伯乐和追随者的支持和帮助。\n\nChenglou 这个人总是在巨兴奋与巨沮丧之间切换，这段时间下来，我开始能感受这种情绪的来源了。\n\n他总是用一句话来总结他回答我的吐槽、抱怨、疑问、惊叹，我就用这句话来结尾好了：\n\n**\"Welcome to the producer side!\"**\n"
  },
  {
    "path": "_posts/2018-09-27-avoiding-success-at-all-cost.md",
    "content": "---\nlayout: post\ntitle: \"Avoiding success at all cost\"\nsubtitle: 'Watching \"Escape from the Ivory Tower: The Haskell Journey\"'\nauthor: \"Hux\"\nheader-style: text\nlang: en\ntags:\n  - Haskell\n  - 笔记\n  - En\n---\n\n\"Avoiding success at all cost\" is the informal motto behinds [Haskell](https://www.haskell.org/). It could be parenthesized in two ways, either \"Avoiding (success at all cost)\" or \"(Avoiding sucess) (at all cost)\". \n\nI'm not going to interpret them directly but rather to share some thoughts on \"the success vs. costs\" basing on my very own understanding and experience.\n\n### The success vs. cost of language design\n\nThere're always trade offs (or compromises) in any software design, and programming language design has no exceptions.\n\nIn other words, all language design decision that made them \"successful\" i.e. being popular and widely-used in industry or education for some reasons, all comes with their own \"costs\": being unsafe, limited expressiveness, or having bad performance, etc.\n\nWhether or not the \"cost\" is a problem really depends on scenarios, or their goals. For instances, Python/JavaScript are both very expressive and beginner-friendly by being dynamically-typed, sacrifing the type safety and performance. Java, in constrast, uses a much safer and optimization-friendly type system but being much less expressive. Another typicial comparison would be memory management in programming languages, where languages that are \"managed\" (by either ARC or Gabage Collector) could be much easier and safer (in terms of memory) for most programmers but also considerred slower than languages that are \"closer to the metal\". \n\nNone of these \"costs\", or \"differences\", really prevent them from being immortally popular.\n\nFor Haskell, the story becomes quite different: being research-oriented means the goal of this language is to pursue some \"ultimate\" things: the \"ultimate\" simplicity of intermediate representation, the \"ultimate\" type system where safety and expressiveness can coexist, the \"ultimate\" compilation speed and runtime performance, the \"ultimate\" concise and elegant concrete syntax, the \"ultimate\"...I don't know. But it has to be some \"ultimate\" things that is very difficult, probably endless and impossible, to achieve. \n\nThis, as a result, made all language decisions in Haskell became very hard and slow, because **almost nothing can be scarified**. That's why Haskell insisted to be lazy to \"guard\" the purity regardless of some problems of being \"call-by-need\"; a decent IO mechanisms is missing in the first 4 yrs after the project's start until P Walder found _Monad_; and the _Type Class_, which is first proposed in P Walder's 1989 paper, spent yrs long to implement and popularize.\n\nAs a side note though, it doesn't mean there is no compromise in Haskell at all. It's just as minimized as it could be during its progress. When one audience asking why we have Haskell and OCaml, which're quite similar in very high level, both survived, SPJ replies:\n\n> There's just a different set of compromises.\n\n### The success vs. cost of language design process\n\nAnother common but extremely controversial (if not the most) topics of programming language design is about its design process: Would you prefer dictatorship or a committee (in other words, a dictatorship of many?)? Would you prefer being proprietary or standardized? In which form would you write the standards, in human nature language, pseudo code, or formal semantics? How many and how frequently breaking changes dare you make? Would you let open source community involve in?  \n\nAgain, I think there is no THE answer for all those questions. Majority of popular programming languages came and are still on going with very different paths.\n\nPython, whose creater, Guido van Rossum, known as the \"Benevolent Dictator For Life\" (BDFL), i.e. good kind of dictator, still play the central role (until July 2018) of the Python's development after Python getting popular and adapt a open source and community-based development model. This factor direcly contribute to the fact that Python 3, as a breaking (not completely backward-compatible and not easy to port) but good (in terms of language design and consistency) revision of the language can still be landed, despite of many communities' pressures. There're many language (Ruby, Perl, Elm) also choose to follow this route.\n\nJavaScript, widely known as being created by Brendan Eich in 10 days, in comparision, quickly involved into a committee (TC39) and standardized (ECMAScript) language due to both the open nature of the Web and fast adoption of itself. But Brendan, as the creater, wasn't even powerful enough to push the committee landing ES4, which is also a breaking but much better revision, but ended up with the ES5 (Harmony), a backward-compatible, yet much less ambitious version due to many political \"fights\" between different parties (e.g. Mozilla, Microsoft, Yahoo etc.) thus the history wasn't changed. Even the latest rising and yearly releasing of the \"modern\" JavaScript (ES6 or ES2015, 2016, 2017...) are mainly driven by the new generation of committee parties (+ Google, Facebook, Airbnb etc.) and still in a very open and standardized way.\n\nAs you can see here, even the history and progress of two rather similar languages can be so different, not to mention more proprietary languages such as Java from Sun/Oracle, C# from Microsoft, OC/Swift from Apple (though the latter was open sourced) or more academia and standardized language like SML and Scheme which both has a standard written in formal semantics.\n\nSo it's not not obvious that Haskell, also chose its own unique process to suit its unique goal. Although it backs on academia, it chose a rather practical/less-formal approach to define the language, i.e. the compiler implementation over standardization (plus many \"formal\" fragments among papers though), which is more like C++/OCaml from this point of view. It has a committee, but instead of being very open and conservative, it's more dictatorial (in terms of average users) and super aggressive in terms of making breaking changes. As a result however, it trained a group of very change-tolerant people in its community...All of these quirks and odds combined works very well and avoid the Haskell \"becoming too success too quickly\".\n\n\n### End thoughts\n\nTo be fair, Haskell has alreay been very \"successful\" nowdays, in particular academia (for education, sexy type laboratory etc.) but also industry, either being used in real business or being very reputable among programmers (as being both hard and fun).\n\nI am not confident and qualified to say Haskell is success in the right degree at the right time. But it's great to see it, after more than 20 and now almost 30 yrs, slowly figure out its very own way, to \"Escape from the Ivory Tower\", and keep going beyond.\n"
  },
  {
    "path": "_posts/2018-10-06-vim-cn-im.md",
    "content": "---\nlayout: post\ntitle: \"Vim 与中文输入法\"\nsubtitle: 'Using Vim with non-english input method'\nauthor: \"Hux\"\nheader-style: text\ntags:\n  - Vim\n---\n\nUpdate: 我最后还是放弃把 Vim 作为主要编辑器来输入中文了，整体使用下来 mental model 的 cost 太重了。记笔记时用用中文呀或者改改博客时偶尔用一下还蛮去，这个时候这个功能至少能帮助你 Esc 之后不煞笔，所以也不算完全没有价值吧……\n\n---\n\n我相信很多中文世界的 Vimer 都遇到过这个烦恼，\b在 vim 的 insert 模式时可能突然想输个中文，输完之后会本能的直接 `esc` 接 normal 模式操作，结果发现跳出来的是中文输入法……\b对于\b vscode，我一般会在几次错误之后被逼到退出 vscode vim 模式，而对于\b终端中用的 neovim，就只能尽量不输入中文了。\n\n\b\b\b为了满足我 1% 用 vim 输入中文的场景（比如写博客），\b我还是想看看有没有什么解决方案，Google 出来\b的解决方案基本是：*在退出 \binsert 模式时记住当时的输入法，并自动切换到默认输入法（一般是英文）给 normal 模式用，并且在下一次进入 insert 模式时再切换回来。*\n\n\b原生 vim 的话，可以使用 [smartim](https://github.com/ybian/smartim) 插件，原理是调用 [im-select](https://github.com/daipeihust/im-select) 这个 CLI 工具来切换输入法。\n\n对于 VSCode-vim 的话，smartim 的移植也在近期的 PR 中被 merge 到了插件里，\b[详情见文档的这部分配置]( https://github.com/VSCodeVim/Vim#use-im-select)，需要指定一下默认输入法和 im-select 的 binary 路径就好。\n\n---\n\b\n\b不过实话说，在 vim 中编辑中文的效率和体验和英文比都是大打折扣的。因为中文分词难度太高，不像英文可以简单依靠一个 `split \" \"` 搞定。所以\u001b其实无论 vim（`w`ord，`b`egin，`e`nd），emacs 还是\b操作系统自带的（比如 macOS 中的 `alt + 箭头`） \u001b「按词\b移动」功能对于中文都仅仅是跳转到下一个空格处而已，对于中文来说基本就是下一句了……其他常用操作诸如 `f`，`/`\u001b, `r`eplace, `t`ill 也都无法很好的工作，基本只能靠 `hjkl` 爬行……\n\n不过也算聊胜于无吧，由于我的主力外置键盘是 HHKB，\b能用 vim 操作的一个子集（`hjkl`, `o`, `A`, `I`, `v` etc.）可能也比按住 `Fn` 的方向键好用……\n"
  },
  {
    "path": "_posts/2019-09-03-vim-from-finder.md",
    "content": "---\nlayout: post\ntitle: \"把「终端下的 Vim」作为 macOS Finder 的打开方式\"\nsubtitle: 'Open file with terminal Vim from the macOS Finder'\nauthor: \"Hux\"\nheader-style: text\ntags:\n  - Vim\n---\n\n我的日常主力编辑器主要是：\n\n- (Neo)Vim\n- Spacemacs (via Emacs-plus)\n- Visual Studio Code\n- IntelliJ IDEA\n\n这里面只有 (Neo)Vim 是存活在终端中的（我并不在终端内使用 Emacs），而由于我日常主要是从终端（via iTerm）来使用电脑，所以会把他们都加入到 `$PATH` 里以方便从终端中唤起，VSCode 和 IDEA 都有一键加入的功能， Emacs 我在 `~/.zshrc` 中放了一个 `alias emacs='open -n -a Emacs.app .'` 解决。\n\n但是，偶尔也会有从 Finder 中打开文件的需求，这时候如果通常会打开拓展名所绑定的 `Open with...` 应用，在大部分时候我的默认绑定是 VSCode，但是今天心血来潮觉得有没有办法直接打开 Vim 呢？搜了一下还真有基于 AppleScript 的解决方案：\n\n1. 打开 `Automator.app`\n2. 选择 `New Document`\n3. 找到 `Run AppleScript` 的 action 双击添加\n4. 编写 AppleScript 脚本来唤起终端与 vim （下文给出了我的脚本你可以直接稍作修改使用）\n5. 保存为 `Applications/iTermVim.app` （你可以自己随便取）\n6. 找到你想要以这种方式打开的文件，比如 `<随便>.markdown`，`⌘ i` 获取信息然后修改 `Open with` 为这个应用然后 `Change All...`\n\n效果超爽 ;)\n\n```applescript\non run {input, parameters}\n  set filename to POSIX path of input\n  set cmd to \"clear; cd `dirname \" & filename & \"`;vim \" & quote & filename & quote\n  tell application \"iTerm\"\n    activate\n    tell the current window\n      create tab with default profile\n      tell the current session\n        write text cmd\n      end tell\n    end tell\n  end tell\nend run\n```\n\n我这里的代码是采取是用 `iTerm` 与唤起 `vim`、窗口置前、在新窗口中打开、同时 `cd` 到目录。你也可以改成用 macOS 自带的 `Terminal.app`、在新窗口而非新 tab 打开、应用不同的 profile、或是执行其他 executable 等……任你发挥啦。\n\n### References\n\n- [Open file in iTerm vim for MacOS Sierra](https://gist.github.com/charlietran/43639b0f4e0a01c7c20df8f1929b76f2)\n- [Open file in Terminal Vim on OSX](https://bl.ocks.org/napcs/2d8376e941133ccfad63e33bf1b1b60c)\n"
  },
  {
    "path": "_posts/2019-09-08-spacemacs-workflow.md",
    "content": "---\nlayout: post\ntitle: \"My Spacemacs Workflow\"\nsubtitle: 'From Vim to Spacemacs'\nauthor: \"Hux\"\nheader-style: text\npublished: false\ntags:\n  - Vim\n  - Emacs\n---\n\nEmacs tend to provide a good support for functional programming languages. Indeed, many FP language community exclusively use Emacs and give only first-party IDE supports to Emacs, such as Coq, Agda, Standard ML, Clojure, etc.\n\nFor the purpose of programming Coq with Proof General, I started to try with Emacs. I quickly found Spacemacs a good alternatives for me...someone had get used to Vim keybindings and want to get some thing useful ASAP w/o configuring a long list as my `.vimrc`.\n\nThough the overall experience is pretty smooth, many quirks about Spacemacs are always being forgotten and had to look up again and again, so I decided to open a note for some specific \"workflow\" that I often used.\n\nYes this is more like a note publishing online for the purpose of \"on-demand accessible\". So don't expect good writing anyways.\n\n\n### Vim-binding\n\nChoose `evil`!\n\n\n### Airline\n\nIt's there!\n\n\n### Nerd Tree / File Sidebar\n\n`SPC f t` for _file tree_. The keybindings for specific operations are very different w/ Vim NerdTree though.\n\n\n### Shell / Terminal\n\nI occasionally use [Neovim's terminal emulator](https://neovim.io/doc/user/nvim_terminal_emulator.html) but in most of the time I just `cmd + D` for iTerms splitted window. \n\nI even mappped `:D` into split-then-terminal to make the experience on par ;)\n\n```vim\ncommand! -nargs=* D  belowright split | terminal <args>\n```\n\nAnyways, Spacemacs does provide a `:shell` that naturally split a window below for terminal. The experience is not very good though.\n\n\n### Tabs / Workspaces\n\nI tend to open multiple _workspace_. Though people might found Vim tabs useful, I am exclusively use iTerm tabs for similar jobs. However Spacemacs is not living in a terminal.\n\n[r/spacemacs - Vim-style tabs?](https://www.reddit.com/r/spacemacs/comments/5w5d2s/vimstyle_tabs/) gave me a good way to approximate the experience by using [Spacemacs Workspaces](http://spacemacs.org/doc/DOCUMENTATION.html#workspaces): `SPC l w <nth>` trigger a so-called \"layout transient state\" (I have no idea what's that mean) to open N-th workspaces, and use `gt`/`gT` to switch between.\n\n\n### Fuzz File Name Search / Rg\n\n`SPC f f`\n\n\n### Buffers\n\n`SPC b b`\n"
  },
  {
    "path": "_posts/2019-11-19-is-pwa-dead-in-2019.md",
    "content": "---\nlayout: post\ntitle: \"2019 年 PWA(Progressive Web App) 凉了吗？\"\nsubtitle: \"Is PWA effectively dead in 2019?\"\nauthor: \"Hux\"\nheader-style: text\ntags:\n  - 知乎\n  - Web\n  - PWA\n---\n\n> 这篇文章转载自[我在知乎上的回答](https://www.zhihu.com/question/352577624/answer/901867825)\n\n作为 PWA 在国内的早期[布道者](https://zhuanlan.zhihu.com/p/25167289)与[实践者](https://zhuanlan.zhihu.com/p/27853228)，我觉得挺凉的。\n以下都是主观感受且 opinion is my own。\n\n**PWA（甚至整个 Web）似乎被 Google（Chrome）与「第三世界」绑到一起去了。**「这世界还有多少人没上过网、没有 4G、没有 3G……印度、印度尼西亚、非洲、乌干达……」这便是这两年的 Chrome Dev Summit 的主旋律了。\n\n而这或许也是现在整个 Google 的主旋律吧，于是便成了 Chrome 和 Chrome 的产品经理们的 KPI（OKR），美其名曰「为了互联网的下一个十亿用户」。我不是说关心第三世界这事不好，但问题在于**你一边嘴上说着 「Open Web、大家的 Web」**，**一边身体上却只想着把 Web 变成「你想要的那个 Web」，然后把其他 Web 的发展方向都「耽误」掉了**。\n\nPWA 的商业案例至今为止，我感到 legit（正当）的仍然只有 twitter，是真正在按一个「给所有用户都能用」的标准来做的。Airbnb/Pinterest/Spotify 可能能及格，而其他的则要么是商业互吹（吹一波走人），要么就是利益（市场导向）一致（Instagram 以及逐年增多的印度系产品）。\n\n我相信很多开发者和我一样对 PWA 的期待本来是作为 RN/Flutter 等跨平台开发的 alternatives（替代品），结果现在连几年前的 [hybrid](https://www.zhihu.com/search?q=hybrid&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A901867825%7D) 方案 Cordova (Phonegap)、Electron的实力都没到， 不要说国内各种自家魔改的 Webview（容器、小程序）了。两年的时间本足以做大量这方面的工作 —— 留学前我还担心是不是两年后我就跟不上 PWA 的发展了，结果现在根本就没什么大动静 —— 每年 CDS 确实都仍然会扔几个新的有关 [capability](https://www.zhihu.com/search?q=capability&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A901867825%7D) 的 API 出来， 但是跟了这么多年 Chrome Dev Summit 我也算是看清了这秀场的节奏 —— 每年扔出来的东西吧，第二年弃坑 2/3，剩下 1/3 就是遛狗 —— virtual list 说了几年了？类似 portal 这样的 [navigation-transition](https://www.zhihu.com/search?q=navigation-transition&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A901867825%7D) 说了几年了？file system API 说了几年了？**我吐槽的点在于，Google（Chrome）你年年画大饼，最后大部分有真正投入资源的，全是你那「第三世界」新兴市场相关的。**作为 Web 开发者你一定知道，有多少新 API 落地到我们日常开发了？\n\nDon't just take my word. 你应该自己去听听这两年的 Chrome Dev Summit，是不是绝大多数的场次都围绕着对「低配」内存、CPU、[网络环境](https://www.zhihu.com/search?q=%E7%BD%91%E7%BB%9C%E7%8E%AF%E5%A2%83&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A901867825%7D)的优化 —— 俨然一副「Web 就只适合在这么不行的地方用」的气息，你一个 Web 开发者都要针对 L1/L2 Cache 优化了你说我们怎么不直接写汇编呢？而单纯谈论 CSS/JS 等 Web 技术的发展，不带使用场景 [bias](https://www.zhihu.com/search?q=bias&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A901867825%7D) 的场次比例一年比一年少。至于 WebAssembly/WebGL/WebXR？—— who TM care? 人家连网都上不了你还想玩 3D/VR 游戏？人家连电脑都没有你还想在 Web 上做生产力工具？WebAssembly 场靠着 Google Research 有点 AI 项目讲了讲 thread 和 SIMD 都能让我感觉到一种与整场会议违和的尴尬……而 3D, VR and AR 总共加起来就做了个六分钟的小短片播，把我乐得。\n\n所谓的 Fugu Project（PWA 项目在 Google 的代号）在我眼里就是 Google**「让 Web 成为他们在第三世界的开发平台」**而准备的一个项目：**官方提及 PWA 最爱提的用户场景不是 Web 的可索引性、可链接性、甚至都不是即开即用（on-demand），而是「用户因为没有流量和 wifi 所以不愿安装[原生应用](https://www.zhihu.com/search?q=%E5%8E%9F%E7%94%9F%E5%BA%94%E7%94%A8&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A901867825%7D)」 。**那几个 dev advocate 反正每年就做些 P 用都没有的玩具在那里自娱自乐自 high，做个扫雷游戏然后说我们这个一路支持到**功能机用键盘来玩**哦……一向亲 JSer 的 Addy Osmani 的 Adaptive Loading 场也来一句「我们要为开 **Data Saver（省流量模式、无图模式**）的用户做优化！」，这世界大概是又回去到了 WAP 的时代吧……而项目带头人 Alex Russell，即便我仍然很感激您过去对 Web 的影响和贡献，但您这两年来动不动就是「怼框架怼友商怼空气」—— 你们这些垃圾框架，居然要 50 KB！你们这些垃圾开发者，用什么框架，Use the platform！（i.e. 用我们的 Web Component （的框架）！）你们这些垃圾浏览器，还不快点支持我们要的 API！ —— 都是你们伤害了我的 W(K)e(P)b(I)！**司马昭之心，路人皆知**。\n\nquote [@尤雨溪](//www.zhihu.com/people/cfdec6226ece879d2571fbc274372e9f)\n\n![](https://pic4.zhimg.com/80/v2-82770d1b0366904c2254908d097e0a60_720w.jpg?source=1940ef5c)\n\n他们在乎的是「下一个十亿用户」，中国显然不在其中呢\n\n\n即便你也是 Web 开发者我也是 Web 开发者，但显然我们已经不是 Google（Chrome）想要支持的 Web 开发者了：当无数 Web 开发者起来为 JS 社区在应用框架的先进性上探索，当我们想要证明 Web 在今天的硬件上终于可以挑战 Native，当我们想要在动画和交互上挑战当年的 Flash，当我们认为 Web 技术已经可以胜任众多[桌面软件](https://www.zhihu.com/search?q=%E6%A1%8C%E9%9D%A2%E8%BD%AF%E4%BB%B6&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A901867825%7D)，当社区期待 Web 能够积极跟进下一个新兴技术（whatever that is）甚至担当重任，当国人在谈论着 5G 的到来 Web 开发者应该做什么时 —— 这个当年挟 Web 以令 OS，推动 Web 平台 state-of-the-art 的 Google（Chrome）却变得让我不认识了。\n\n\n**Web 自诞生以来便就是个[同床异梦](https://www.zhihu.com/search?q=%E5%90%8C%E5%BA%8A%E5%BC%82%E6%A2%A6&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A901867825%7D)的地方 —— Web 也因此永垂不朽；而对于 PWA 能在当前 Google 的主导下迅速发展成一个有竞争力的跨平台开发解决方案这事儿，或许只是其中的又一黄粱美梦罢了。**\n\n\n\n**补充两点：**\n\n*   我支持「小程序」的产品价值，也支持 PWA 作为 Web 开放标准一部分的技术价值。\n*   PWA 目前主要靠 Google 推动是客观事实，且 PWA 的发展必须依赖平台（浏览器）的参与。\n"
  },
  {
    "path": "_posts/2020-04-03-react-hooks-vue-composition.md",
    "content": "---\nlayout: post\ntitle: \"React Hooks 是否可以改为用类似 Vue 3 Composition API 的方式实现？\"\nsubtitle: \"Thinking in React vs. Thinking in Vue\"\nauthor: \"Hux\"\nheader-style: text\ntags:\n  - 知乎\n  - Web\n  - React\n---\n\n> 这篇文章转载自[我在知乎上的回答](https://www.zhihu.com/question/378861485/answer/1125724740)\n\n\n**不能，因为是很不一样的[心智模型](https://www.zhihu.com/search?q=%E5%BF%83%E6%99%BA%E6%A8%A1%E5%9E%8B&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A1125724740%7D)（Mental Model）。**我觉得很多同学只关注到了这两套 API 在功能上都能复用逻辑的相似点，而低估了两个框架体系「大背景」上的差异。\n\n正文开始前我先声明一下，\n\n1.  一是本文观点不代表公司。我是觉得圈子里不认同 Hooks 的声音太多了（比如 [@徐飞](//www.zhihu.com/people/c5198d4e9c0145aee04dd53cc6590edd) 叔叔、 [@贺师俊](//www.zhihu.com/people/3ec3b166992a5a90a1083945d2490d38) 贺老、 [@题叶](//www.zhihu.com/people/790dccce26904cdcd11b0fad3bac37b7) 同学等老朋友 no offensive），所以自愿出来平衡一下。\n\n2.  二是我确实好久没有实际写前端了 ，React Hooks 实战还不多，Vue 3 只草草略读了 [Composition API RFC](https://link.zhihu.com/?target=https%3A//vue-composition-api-rfc.netlify.com/) 与之前中文的 [Vue Function-based API RFC](https://zhuanlan.zhihu.com/p/68477600)（所以对细节并不太熟悉。）欢迎大家务必指正、补充（与要求补充）。\n\n引言\n--\n\n「框架/库是编程语言上的抽象」，并不意味着框架的设计就无法跳脱出其实现语言的习语（idioms）与编程范式（paradiams）。\n\n得益于 JavaScript 几个非常 Lisp 的特点：一等公民函数、动态类型、一定的宏支持（比如 Babel），这几年[前端框架](https://www.zhihu.com/search?q=%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A1125724740%7D)的发展可以看到很多编程语言设计的思路：框架成为了由 DSL 与 API 构成的特定语法（syntax）、从 JavaScript 中扬弃以及由 API 附加的语义（semantics）、支撑这套体系运作的运行时（runtime）、以及所表达的心智模型（[mental model](https://www.zhihu.com/search?q=mental+model&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A1125724740%7D)）的结合。\n\n\n\nVue 3, \"Reactive (Closure-based) OOP\"\n-------------------------------------\n\n先来看 Vue（本文中的 Vue 主要指使用 Composition API 下的 Vue）\n\n```ts\nconst Counter = {\n  setup(initialProps) {\n    const count = reactive({count: 0}) // or `ref(0)`\n    const inc = () => { count.value++ }\n    return {count, inc}\n  }\n  template: \"...\"\n}\n```\n\n\nVue 为组件挑选的语义是「对象」：Composition API 的`setup` 只会调用一次并返回一个对象合并到 `Counter` 组件上，这个对象与其成员全都是持久的引用，包括保存在 `inc` 闭包中的状态 `count` 也是持久的。渲染则是对组件上的`template` 域的具象化。\n\nVue 附加的核心语义是（基于可变数据的）「响应式（reactive）」：状态 `count` 是一个响应式对象，`inc` 进行状态更改的方式是对 `count` 直接修改，状态更改的结果是执行所有观察者（watcher）的逻辑，包括重渲染和执行副作用（`watchEffect`) ，都是基于这个语义纳入数据流。\n\n有的同学（比如题主）说，如果改成返回一个 `render` 函数，**直接利用闭包来保存组件变量**，你还说这是对象的语义吗？\n\n```ts\nreturn (props) => <a onClick={inc}>{count.value}</a>\n```\n\n\n_是的_。Vue 的实现需要保持这个函数与其闭包的引用不变（referential identity）来满足状态被持久化的语义，是 JavaScript 用闭包模拟对象私有属性的经典模式[\\[1\\]](#ref_1)。（「闭包是穷人的对象，对象是穷人的闭包」）\n\n为了帮助你理解，如果我们有一门假想的 Vue 语言的话……\n\n```ts\n// hypothetical Vue lang\ncomponent Counter (props) {    // constructor\n  @reactive count = 0          // field\n  inc() { count.value ++ }     // method\n  render() { return <a onClick={inc}>{count.value}</a> }\n}\n```\n\n\n是不是有基于类的 OOP 内味了？只不过 Vue 的对象是基于单例（或者闭包）而非类（或原型）实现，以及成员是被施过 reactive 魔法哒！这里我们展示了如何从概念上将 Vue 归约到 OOP 的心智模型，不过需要注意得是，Vue 整个体系（考虑状态管理、[数据流控制](https://www.zhihu.com/search?q=%E6%95%B0%E6%8D%AE%E6%B5%81%E6%8E%A7%E5%88%B6&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A1125724740%7D)）的心智模型有很多 FP 的东西在里面，仍然和传统观念（比如 Java）的 OOP 有很大差别，[\\[2\\]](#ref_2)[\\[3\\]](#ref_3)。\n\nVue 运行时的核心是 dependency tracking（依赖追踪），首先它使得 reactive 语义对用户相对 implicit（隐式），依赖都是自动收集的，大大降低了用户的心智负担。其次呢它非常细的跟踪粒度配合 Vue 使用静态化程度比较高模板使得重渲染自动就可以做到非常精准。\n\n总结起来，Vue 在组件方面的心智模型仍然是「拥有数据与行为且自响应式的对象」，只要照着这个思路去想，就比较好理解「为什么 Vue 的状态可以使用可变数据结构」、「为什么 Vue 需要`ref` 包装值类型」，以及 RFC 在对比 React Hooks 时提到的「为什么 Vue 更接近大家习惯的 JS 」（这点比较主观就是了）、「为什么 Vue 的 GC 压力会更小」、「为什么 Vue 不需要手动声明依赖」等优势的由来了。\n\n\n\nReact, \"Purely (Semi-Monadic/Algebraic) FP\"\n-------------------------------------------\n\n再来看 React（本文中的 React 主要指 Hooks API 下的 React）\n\n```ts\nfunction Counter(props) {\n  const [count, setCount] = React.useState(0);\n  const inc = () => setCount(count + 1)\n  return <a onClick={inc}>{count}</a>\n}\n```\n\nReact 为组件挑选的语义是「函数」，每次渲染都是对 `Counter` 这个函数的一次真实调用。每次 `useState` 都会执行并从 React 那取出当前的状态给 `count`，每次也都会创建一个新的 `inc` 函数（故其闭包中捕获的也是新的 `count` 值）。\n\nReact 附加的核心语义是一个副作用受控的「执行上下文（evaluation context）」，通俗得说就是 React 这个运行环境：状态 `count` 每次都要从 React 上下文中取出，`inc` 对状态更改的方式是用 `setCount` 更新上下文里的内容，状态更改的结果是这个函数会被重新调用，调用时函数就会从新的上下文中获得新的状态、进行重渲染和安排上（schedule）受上下文控制的副作用（`useEffect`) 。\n\n为了帮助你理解，如果我们有一门假想的 React 语言的话……\n\n\n```ts\n// hypothetical React lang Ⅰ\ncomponent Counter = (props) =>      // function\n  @context.state(1) {                  // context provides `get_or` and `put`\n    count <- get_or(0)              // get from context (or use default value)\n    let inc = () => put(count + 1)  // callback can update the context\n    return <a onClick={inc}>{count}</a>\n  }\n```\n\n是不是有基于 Monad 的 Haskell 内味了？只不过 React 把 API 做得完全不需要你弄懂这些复杂的东西[\\[4\\]](#ref_4)。如果你不熟悉 Monad 这个纯 FP 的概念，我们可以先不严谨[\\[5\\]](#ref_5)得把它当做文中的「上下文」。扔出 M-bomb 的原因是大家通常把它作为在纯 FP 中处理副作用的标杆，帮助我们展示如何把 React 归约到纯 FP。\n\n有的同学（比如[@题叶](//www.zhihu.com/people/790dccce26904cdcd11b0fad3bac37b7)）会疑惑，这怎么跟我认得的「纯函数」不一样呢，这也是「纯函数式编程」吗？其实如果我们把语法糖展开就会变成：\n\n```ts\ncomponent Counter = (props) =>\n  context.state(1).get_or(0).then([count, put] => {  // `then` is monadic bind.\n    let inc = () => put(count + 1)\n    return <a onClock={inc}>{count}</a>\n  }).unwrap() // assuming it's safe.\n```\n\n有没有想到同样被称作 Monad，具备异步上下文的 Promise？\n\n再（过度）简化一些，你可以想象成最直白的 state-passing 风格（实际上 2018 年时 React 团队就这么考虑过类似的 API [\\[6\\]](#ref_6)，也是 Seb 的理论基础之一[\\[7\\]](#ref_7)）:\n\n```ts\ncomponent Counter = (props, stateMap) => {\n  let count = stateMap.get(1, or=0);\n  let inc = () => stateMap.set(1, count + 1); // functional update\n  return <a onClick={inc}>{count}</a>\n}\n```\n\n\n不过，React 从实现到 API 设计都更靠近与追求[\\[8\\]](#ref_8)的心智模型是一个相对较新的纯 FP 概念 —— Algebraic Effect（代数作用），虽然名字听起来相当迷惑，但其实它在描述副作用上比 Monad 反而更不花哨（少一些 ceremony），理解起来也更加容易，Dan 有一篇给 JSer 看的很易懂的博文[\\[9\\]](#ref_9)并且有中文翻译[\\[10\\]](#ref_10)。我们可以先把它当作「可以重新恢复的 `try-catch` 」\n\n为了帮助你理解，如果我们又又又又有一门假想的 React 语言的话……\n\n```ts\n// hypothetical React lang Ⅱ\ncomponent Counter = (props) => {\n  let count = perform getState(1),or=0); // 1. `perform` \"throw\" effects to the context\n                                                // 4. resume with the continuation to here\n  let inc = () => perform putState(1, s=count + 1);\n  return <a onClick={inc}>{count}</a>\n}\n\n// call site\ntry <Counter /> handle  // 2.try-handle pattern match effects\n                        // 3. get state from the context and then resume\n| getState(key, or) => resume with context.state(key) ?? or\n| putState(key, s) => context.state(key)=s; resume with void\n```\n\n是不是有感觉一些了？我们从组件里「扔出去」去更改 「执行上下文」里的状态，然后再「恢复」回来……\n\n即便 React 已经很努力的降低了 API 的门槛，但其思维的愈加纯函数式确实会在更多程序员眼里非常「离经叛道」。\n\n所以为什么我们需要「纯函数式编程」？抛开大家可能已经熟悉的声明式、数据流清晰、局部推理、易于组合外，其背后的学术理论支撑使得其在编译期静态分析与优化、运行时高并发高并行友好方面都有极高的理论上限和上升空间（近年编程原理的理论研究完全是被函数式编程霸占得）\n\nReact 现在运行时侧重的核心是 cooperative multitasking（协作式多任务），来为 React 加入 concurrency（并发）、schedule（调度）等底层能力。很多同学只听说过后端的高并发，其实像多任务操作系统这样的「终极 UI」就是一个高并发且依赖诸如分时（time-slicing）、优先处理（re-priortizing）等进程调度的场景。React 希望把这些技术带给 UI 开发者（比如 Suspense，比如 Selective Hydration，比如 RN 新架构中的 Fabrics），第一步是运行时用 Fiber 架构重写不依赖原生调用栈，第二步就是用 Hooks 解决 Class API 在纯度上约束力不够的问题。不纯的组件在 React 并发模式下很容易出现数据竞态（data race）的问题。\n\n总结起来，React 在组件方面的心智模型是「副作用受上下文托管的纯函数」，只要照着这个思路去想，就比较好理解「为什么 React 中倾向于使用不可变数据结构」、「为什么 useEffect 默认会执行 cleanup 来保持幂等性」、「为什么 React 需要 `useRef` 这样的跨渲染 ref cell 机制来做 mutable ref 」、「为什么 React 的性能优化是 `useMemo`, `Memo` 这样 FP 风格的 memoization 」、「为什么 React 需要 `useMemo` `useCallback` 来保持 referential identity 」、「为什么 React 需要用依赖列表来进行 cache invalidation」等问题了。\n\n\n\n补充\n--\n\n2016 年底时我就觉得 React 和 Vue 的（一个）终极区别在于「可变」还是「不可变」。\n\n![](https://pica.zhimg.com/50/v2-22e50932c60262bf381456de3017b217_720w.jpg?source=1940ef5c)\n\nSeb 在 Hooks 发布后收到一些质疑的 brain dump[\\[11\\]](#ref_11) 里写到：\n\n> It's interesting because there are really two approaches evolving. There's a **mutable + change tracking** approach and there's an **immutability + referential equality testing** approach. It's difficult to mix and match them when you build new features on top. So that's why React has been pushing a bit harder on immutability lately to be able to build on top of it. Both have various tradeoffs but others are doing good research in other areas, so we've decided to focus on this direction and see where it leads us.\n\n不全部翻译了，说得是整个「大前端」社区里最主要的两条道路分歧：\n\n* 可变 + 变更追踪。包括 Vue，Angular，\n* 不可变 + 引用相等性。包括 React，Elm，(Flutter?)\n\n这个分歧其实与我之前行文的侧重点「为组件挑选的语义」其实是对偶的：\n\n* 前者是对传统 Imperative Programming（包括 OOP）思路的一种增强，加入了 Reactivity。\n* 后者则是传统 Functional Programming 在 UI 开发领域的发扬光大（Functional Reactive Programming?)，只不过 React 是用一种比较「超越 JS 语言」的方式去实现得。\n\n这两条道路从底子就很不同，所以才造成了 React 和 Vue 在大家眼里的渐行渐远吧。\n\n不过最近多看了一些 Svelte、 SwiftUI[\\[12\\]](#ref_12)和 Jetpack Compose[\\[13\\]](#ref_13)也开始有了一些殊途同归的感觉，Props 无论是跟着 View 销毁还是函数参数总是暂时性的输入，States 无论跟着组件实例还是置外总必须是持久化的，至于怎么判断更新，像 `array.push` 这种 mutable state 的场景总是不好 track 得，于是就只能各显神通了：React 想通过 reference equality 自动、Vue 3 想通过 Proxy 自动，但其实只要能把 change set 搞出来就行，Vue2/Svelte/SwiftUI/Compose 这些让用户手动给提示得不是也工作得好好得吗？只要能把变更集算出来传递给视图层，那视图层就只管更新（rerender/rebuild/recomposite）就是了。\n\n补充 2\n----\n\n如果是在重度依赖 Flux (Vuex/Redux, whatever) 的场景，可能 Vue/React 会更像是一个只负责渲染的 dumb/passive layer，这种时候上文说的 Vue/React 的差异会显得不明显，因为大部分的状态管理（state management）都已经扔到更外层去做了。\n\n不过，考虑需要组件内聚的场景（即组件自己有私有状态，需要 self-conatined）以及 React Hooks / Vue Composition APIs 开始接管更多（除状态之外的，比如 IO）副作用，这种差异只会变得越来越明显得。\n\n\n\n以上。\n\n参考\n--\n\n1.  JavaScript 模块化七日谈 - 黄玄的博客 Hux Blog [https://huangxuan.me/2015/07/09/js-module-7day/](https://huangxuan.me/2015/07/09/js-module-7day/)\n2.  如何理解尤雨溪在 2019 VueConf 上所讲的 UI 类框架很少使用面向对象的特性这件事？- 黄玄的回答 [https://www.zhihu.com/question/328958700/answer/714287394](https://www.zhihu.com/question/328958700/answer/714287394)\n3.  前端是否适合使用面向对象的方式编程？- 黄玄的回答 [https://www.zhihu.com/question/329005869/answer/739525268](https://www.zhihu.com/question/329005869/answer/739525268)\n4.  React Hooks的引入会对之后的React项目开发产生什么影响？- 黄玄的回答 [https://www.zhihu.com/question/302916879/answer/536846510](https://www.zhihu.com/question/302916879/answer/536846510)\n5.  React 上下文的组合是通过调用顺序在运行时里维护一个链表而非基于参数化多态的层叠（比如 Monad Transformer）来表达，可以看到都是线性的。\n6.  State-passing Style Hooks [https://mobile.twitter.com/acdlite/status/971598256454098944](https://mobile.twitter.com/acdlite/status/971598256454098944)\n7.  [https://github.com/reactjs/react-basic](https://github.com/reactjs/react-basic)\n8.  可以把上下文或者 Hooks 的调用视为一次 stack unwinding + resume continuation。同样，考虑 row polymorphism 也是线性的。\n9.  Algebraic Effects for the Rest of Us [https://overreacted.io/algebraic-effects-for-the-rest-of-us/](https://overreacted.io/algebraic-effects-for-the-rest-of-us/)\n10.  通俗易懂的代数效应 [https://overreacted.io/zh-hans/algebraic-effects-for-the-rest-of-us/](https://overreacted.io/zh-hans/algebraic-effects-for-the-rest-of-us/)\n11.  Why React [https://gist.github.com/sebmarkbage/a5ef436427437a98408672108df01919](https://gist.github.com/sebmarkbage/a5ef436427437a98408672108df01919)\n12.  [https://swiftwithmajid.com/2019/06/12/understanding-property-wrappers-in-swiftui/](https://swiftwithmajid.com/2019/06/12/understanding-property-wrappers-in-swiftui/)\n13.  [https://developer.android.com/jetpack/compose/state#remember](https://developer.android.com/jetpack/compose/state#remember)\n"
  },
  {
    "path": "_posts/2020-07-05-reflection-2020.md",
    "content": "---\nlayout: post\ntitle: \"作为一个前端，看不懂@黄玄 的几乎每一个回答，只有我自己吗？\"\nsubtitle: \"Taking this chance to reflect on myself\"\nauthor: \"Hux\"\nheader-style: text\ntags:\n  - 知乎\n  - Meta\n---\n\n> 这篇文章转载自[我在知乎上的回答](https://www.zhihu.com/question/403735935/answer/1321904076)\n\n我也看不懂。\n------\n\n对于任何一个我有一定了解的领域，我都知道一大堆我看不懂的东西。反而是对于那些我一点都不了解的，我甚至都说不出来我不懂什么。\n\n有的时候我会觉得，在我眼里还只有前端的时候，我还更自信更爱分享一点。可能因为那时候我能感知到的「边界」就只有 2^4 = 16 这么大，还觉得自己满打满算已经懂了 4 吧。打个比喻的话就是觉得自己已经能干活了，但还想再了解下 JS 引擎、浏览器、框架等的工作原理，或许还想再多学点后端和移动端当个全栈？总之 4/16 已经是「全集」的 25% 了，觉得自己还挺棒棒哒。\n\n结果等我的「知识」真成长到 16 时，才意识到「欧，原来计算机科学还有这么多东西」？而且每个领域水都深得很，教科书里引论文，论文里再引更多论文，像一棵棵树般不断分形出去…认知的「边界」也相应的长到了 2^16 = 65536。自己懂的东西只占 0.02%，一下觉得自己真是什么都不是了。我把这个瞎掰称之为「认知的指数成长理论」。\n\n而我能做的就是学会与这样的认知和平共处。Prof. Matt Might 画的那篇 The illustrated guide to a Ph.D[\\[1\\]](#ref_1)（[「博士是什么」](https://zhuanlan.zhihu.com/p/19789670)) 让我意识到个体在「人类所有知识」面前的渺小，而「成长」的过程，大概就是在那个愈发巨大的「看不懂集」里，挑选出你还愿意继续去「探索」的那些「子集」吧。\n\n![](https://picx.zhimg.com/50/v2-9557bd0507ca70f7afd075730f31a2e3_720w.jpg?source=1940ef5c)\n\n![](https://picx.zhimg.com/80/v2-9557bd0507ca70f7afd075730f31a2e3_720w.jpg?source=1940ef5c)\n\n「认知的指数成长理论」\n\n作为一个前端。\n-------\n\n在相当长的一段时间里，「前端」既是我的兴趣也是我的职业，那时好像不需要有区别 —— 从早早在阿里实习，到还没毕业就在微影带团队，小中大的公司都待过，活动也参加了不少。其实如果就这样专注于「作为一个前端」，应该现在也还混得不赖吧。\n\n可是偏偏你就发现，那个「看不懂集」的边缘总在发着光 —— 群友形容有一部人的动力在于「理解驱动」，我想了想，那或许是我「半路出家」积累的太多疑惑需要解答；又或许，我可能只是想要「旅游」吧 —— 在高三从重点班理科生转了艺术，在阿里从交互又转了前端，可我还有好奇的理论、又或者还没尝试过系统编程，又或者是下一个有趣的产品形态……想去探索下一件事的欲望总是逐渐盖过了舒适感，你听说了那个学科，你听说了那个文明，你听说了风暴中心，可是你不去看，你就永远不知道那里是什么样。\n\n最近多次被问及「前端团队的方向是什么？」才突然意识到自己有一段时间不这样思考了 —— 这个问题天生就带着市场环境强调[精细化分工](https://www.zhihu.com/search?q=%E7%B2%BE%E7%BB%86%E5%8C%96%E5%88%86%E5%B7%A5&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A1321904076%7D)的倾向，而相反地，有时我惊讶于 Facebook 内部「疏于管理」得就像个[开源软件](https://www.zhihu.com/search?q=%E5%BC%80%E6%BA%90%E8%BD%AF%E4%BB%B6&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A1321904076%7D)的菜市场。前端作为一个子领域当然需要特定的技术设施与人员的专长，但或许你我的职业/兴趣发展、公司的组织架构、软件的开发方式，不一定都非得绑上。\n\n我想要追求的状态不只是能继续做我已经擅长的事情，我还想去探索那些我想要尝试但可能还不太擅长的事情，最后再顺便把钱赚着 —— 就美其名曰「技术自由」吧。所以你说，我是前端吗，我不是前端吗？\n\n![](https://pic4.zhimg.com/50/v2-6caf0e597779eb690dffe71c0c610f54_720w.jpg?source=1940ef5c)\n\n![](https://pic4.zhimg.com/80/v2-6caf0e597779eb690dffe71c0c610f54_720w.jpg?source=1940ef5c)\n\n「专注」和「职业」又何必要是一个双射呢？\n\n\n\n回答。\n---\n\n答题不是我的工作，我不做培训也不靠这个赚钱，不愿花时间琢磨如何哗众取宠，  \n只是有时恰好有新的感悟可以分享，有时恰好有有趣的题目能引发我的思考；\n\n答题只是博客之外另一个用写作自我沉淀的地方罢了，难免会有点自我，  \n但反正也只是写给同路人看的，也为了发现与认识更多的同路人；\n\n我不愿意「只」为了答题而写字，也还是希望言之有物，  \n这道题如此 meta，是回答给谁呢，又或是回答给我呢？\n\n结语。\n---\n\n有的时候需要赚钱，有的时候需要理想。  \n你是更想做一位黑客与画家，还是想站在 Hilbert 第十问题的肩上。\n\nGödel 说太难，人生又怎会比 Peano 简单。  \n面对的 [tradeoff](https://www.zhihu.com/search?q=tradeoff&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A1321904076%7D) 太多，才只能去近似一个想要的模样。\n\n说得都是走心的话，走脑还是多看书吧。  \n如果能给在读的你带来一点不同思考，那便是对一个碳基生物所能给予的最高嘉奖了。\n\n\n\n黄玄，\n二〇二〇年七月五日，\n于美国[圣塔克拉拉](https://www.zhihu.com/search?q=%E5%9C%A3%E5%A1%94%E5%85%8B%E6%8B%89%E6%8B%89&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A1321904076%7D)。\n\n\n_「有一部分朋友知道，我现在在探索的事情跟前端关系还挺近的啦。_\n_希望我也能突破我自我驱动的局限性，多做一些更落地的事情吧！共勉！」_\n\n参考\n--\n\n1.  The illustrated guide to a Ph.D [http://matt.might.net/articles/phd-school-in-pictures/](http://matt.might.net/articles/phd-school-in-pictures/)\n"
  },
  {
    "path": "_posts/2021-01-19-the-systematic-failure-of-higher-education-in-china.md",
    "content": "---\nlayout:     post\ntitle:      \"中国高等教育的系统性失败\"\nsubtitle:   \"The Systematic Failure of Higher Education in China\"\ndate:       2021-01-19 12:00:00\nauthor:     \"Hux\"\ncatalog: false\npublished: false\nheader-style: text\ntags:\n  - 被夹\n---\n\n> 该回答在知乎问题[「如何评价上海交通大学 18 级计算机系第一名「迟先生」的言论？」](https://www.zhihu.com/question/439622084/answer/1685314467) 下无原因被夹。\n> 询问我的「专属小管家」多次后仍然给不出任何原因与具体修改意见，自己多次尝试小幅修改无果，干脆直接将原文发上来吧。\n\n\n高票 [@Youngster38324 的回答](https://www.zhihu.com/question/439622084/answer/1681505518)透露出来的本质上是「**中国高等教育的系统性失败**」，逐层来看:\n\n1. 「**进大学前唯分数和同质化教育**」导致了太多人去大学后根本不知道自己要干嘛，很多人专业根本就不是自己选的更不要说知道自己有没有兴趣了，即便是很多高分考生也路径依赖地以为继续努力填鸭就能成功，没有意识到高考后的人生已经换赛道了。这里对比美国高中生的 AP（Advanced Placement）预科制度以及整体社会鼓励向自我发展看而不是向钱看的风气。\n\n2. 「**进大学后专业制度没有容错性**」，即便已经发现自己不喜欢被录取专业了也没有办法，因为转专业制度毫无人性（通常要求你先要在你已经不喜欢了的本专业内卷到班级前多少名）。对比美国本科，专业可以 undecided（先上课再决定专业，比如经典的哈佛 CS50，你上下来感兴趣了再去选 CS）；学位本身只是某个学科下课程学分累计的自然结果，因此可以灵活的转专业与多学位；班级这种促进内卷的概念被弱化，强调跟自己比关注个人的成长，学生自己控制上几年课，念几个学位，中间休学一下都没关系。\n\n3. 前两步的结果就是导致大学为了毕业率把「**评判标准搞成了大锅饭平均主义**」，为了能够每年顺利向社会输送一批（80% 将来都不会从事本专业）的人才，打分根据每年学生情况动态规划自适应，把大学搞成了「严进宽出」。相反美国的大学教育强调「宽进严出」，无论你底子如何，无论重修几次，你只要通过了某个（相对稳定的）客观标准就是合格，为了保证该制度的机械性运作，辅以严格的日常作业计分，对「作弊」零容忍（自动化判重，发现一次重修两次退学）。\n\n4. 平均主义进一步导致「**课程设置没有灵活性无法自定义**」，老师不但要照顾及格率还有一颗圣母心希望那些对专业没什么兴趣的人能好歹学到点东西，同时又真心欣赏且想要给予好学的尖子生资源，最后即便绞尽脑汁了也还是只能弄出个在差生里下限高在尖子生里上限低的课程安排两面不当人 —— 尖子生觉得课程要求太低不能激发自己的潜力喂不饱（常见于私下要求加难度或者去无学分旁听），摸鱼的觉得老师影响了我的快乐学习（常见于课堂上一布置作业下面就叫苦连天）该挂还是挂或者 60 分万岁。对比美国，学生的课程表可以自行安排，通常一个课会开多个班次照顾灵活性，学霸可以比别人多上任意节课，也可以跨专业选课或者减少课程增加实习或研究，而且难度自选只要你点过前置技能就行。\n\n有类似迟先生这样诉求的人很多，可是一个系统很难由系统内的个体改变，所以很多个体选择了做局部优化趋利避害陪玩成为既得利益者，或者全局优化更换自己所处的系统。\n\n只要所处系统里的大部分个体都已经默许了这个游戏规则，无论迟先生是「凡尔赛」还是「理想主义」，改变赛道规则就会被其他个体认为侵害到利益。小孩才分对错，成年人的屁股都是歪得，都是各取所需。\n\n**都是这个时代的缩影。**\n"
  },
  {
    "path": "_posts/2021-04-10-js-20yrs-preface.md",
    "content": "---\nlayout:       post\ntitle:        \"《JavaScript 二十年》推荐语\"\nauthor:       \"Hux\"\nheader-style: text\ncatalog:      true\ntags:\n    - Web\n    - JavaScript\n---\n\n> 雪碧（doodlewind）邀请我给[《JavaScript 二十年》](https://zhuanlan.zhihu.com/p/373065151) 写的推荐序。\n\nJavaScript 常常被戏称为一门偶然成功的玩具语言。而实际上，它出身名门，更是成长在聚光灯之下。纵观历史，有资格被标准化的编程语言甚少，它因此成为多方角力的战场，却也有幸同时得到业界与学界先驱的亲传。时至今日，我们甚至难言是它背负了太多妥协，还是这些妥协才成就了它呢。以史为鉴，或许你会有自己的答案。\n\n— 黄玄，Facebook 软件工程师（编程语言、JS 引擎、前端基础设施）、中文前端社区活跃成员。\n"
  },
  {
    "path": "_posts/cs_idols/2019-09-13-peter-john-landin.md",
    "content": "---\ntitle: \"Peter John Landin\"\nsubtitle: \"「计算机科学偶像」- 彼得·约翰·兰丁\"\nlayout: post\nauthor: \"Hux\"\npublished: false\nheader-style: text\ntags:\n  - CS Idols\n---\n\n> - [wiki](https://en.wikipedia.org/wiki/Peter_Landin)\n> - [维基](https://zh.wikipedia.org/wiki/%E5%BD%BC%E5%BE%97%C2%B7%E5%85%B0%E4%B8%81)\n\nI was long curious about how does λ calculus become the foundation of formalizaing programming languages. It's strange that I haven't look up the answer until today: It's invented so early by Alonzo Church (whom I will write another post for) as an alternative mathematic foundation in 1930s and its relation with programming language was re-discoverred in 1960s.\n\nFrom the \"Lambda calculus and programming languages\" section of wikipedia page:\n\n> As pointed out by Peter Landin's 1965 paper \"A Correspondence between ALGOL 60 and Church's Lambda-notation\"\n\nI found this name quite familiar since I read his paper \"The mechanical evaluation of expressions\" before, in which he introduced the first abstract machine for functional programming language, namely [SECD machine](https://en.wikipedia.org/wiki/SECD_machine). This paper also define the term [Closure](https://en.wikipedia.org/wiki/Closure_(computer_programming)) which becomes a prevalent notion in computer programming nowadays.\n\nBesides of that, his contributions also include:\n\n- on ALGO definition\n- [ISWIM](https://en.wikipedia.org/wiki/ISWIM) programming language\n- [off-side rule](https://en.wikipedia.org/wiki/Off-side_rule), known as \"indentation-based\" syntax nowadays, popularized by Miranda, Haskell, Python, etc.\n- coin the term [syntactic sugar](https://en.wikipedia.org/wiki/Syntactic_sugar)\n\nHe was much influenced by a study of McCarthy's LISP and taught [Tony Hoare](https://en.wikipedia.org/wiki/Tony_Hoare) ALGO with Peter Naur and Edsger W. Dijkstra. (Oh yes, definitely 4 more people to write).\n\nI have just download his old, influential paper \"The next 700 programming languages\".\nI am sure it will be an enjoyable read.\n"
  },
  {
    "path": "_posts/data_rep/2020-06-19-data-rep-int.md",
    "content": "---\ntitle: \"Data Representation - Integer\"\nsubtitle: \"「数据表示」整数\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - 笔记\n  - 基础\n  - C\n  - C++\n---\n\nIntegers, or _whole number_ from elemental mathematics, are the most common and\nfundamental numbers used in the computers. It's represented as\n_fixed-point numbers_, contrast to _floating-point numbers_ in the machine.\nToday we are going to learn a whole bunch of way to encode it.\n\nThere are mainly two properties to make a integer representation different:\n\n1. **Size, of the number of bits used**.\nusually the power of 2. e.g. 8-bit, 16-bit, 32-bit, 64-bit.\n\n2. **Signed or unsigned**.\nthere are also multiple schemas to encode a signed integers.\n\nWe are also gonna use the below terminologies throughout the post:\n\n- _MSB_: Most Significant Bit\n- _LSB_: Least Significant Bit\n\n\nPrerequisite - `printf` Recap\n----------------------------------------\n\nWe will quickly recap the integers subset of usages of `printf`.\nBasically, we used _format specifier_ to interpolate values into strings:\n\n### [Format Specifier](http://www.cplusplus.com/reference/cstdio/printf/)\n\n> `%[flags][width][.precision][length]specifier`\n\n- `specifier`\n  - `d`, `i` : signed decimal\n  - `u` : unsigned decimal\n  - `c` : char\n  - `p`: pointer addr\n  - `x` / `X` : lower/upper unsigned hex\n- `length`\n  - `l` : long (at least 32)\n  - `ll` : long long (at least 64)\n  - `h` : short (usually 16)\n  - `hh` : short short (usually 8)\n\n```cpp\nusing namespace std;\nint main()  {\n  cout << \"Size of int = \"<< sizeof(int) << endl;\n  cout << \"Size of long = \" << sizeof(long) << endl;\n  cout << \"Size of long long = \" << sizeof(long long);\n}\nOutput in 32 bit gcc compiler: 4 4 8\nOutput in 64 bit gcc compiler: 4 8 8\n```\n\n### [`inttypes.h` from C99](http://www.qnx.com/developers/docs/6.5.0/index.jsp?topic=%2Fcom.qnx.doc.dinkum_en_c99%2Finttypes.html)\n\nAlso in [cppreference.com](https://en.cppreference.com/w/c/types/integer)\n\n```cpp\n// signed int (d or i)\n#define PRId8     \"hhd\"\n#define PRId16    \"hd\"\n#define PRId32    \"ld\"\n#define PRId64    \"lld\"\n\n// unsigned int (u)\n#define PRIu8     \"hhd\"\n#define PRIu16    \"hd\"\n#define PRIu32    \"ld\"\n#define PRIu64    \"lld\"\n\n// unsigned hex\n#define PRIx8     \"hhu\"\n#define PRIx16    \"hu\"\n#define PRIx32    \"lu\"\n#define PRIx64    \"llu\"\n\n// uintptr_t (64 bit machine word len)\n#define PRIxPTR   \"llx\"\n```\n\n\nUnsigned Integers\n-----------------\n\nThe conversion between unsigned integers and binaries are trivial.\nHere, we can represent 8 bits (i.e. a _byte_) as a _hex pair_, e.g.\n`255 == 0xff == 0b11111111`.\n\n```cpp\n#include <stdint.h>    // uintN_t\n#include <inttypes.h>  // PRI macros\n\nuint8_t u8 = 255;\nprintf(\"0x%02\" PRIx8 \"\\n\", u8); // 0xff\nprintf(  \"%\"   PRId8 \"\\n\", u8); // 255\n```\n\n\nSigned Integers\n-----------------\n\nSigned integers are more complicated. We need to cut those bits to halves\nto represent both positive and negative integers somehow.\n\nThere are four well-known schemas to encode it, according to\n[signed number representation of wikipedia](https://en.wikipedia.org/wiki/Signed_number_representations).\n\n### Sign magnitude 原码\n\nIt's also called _\"sign and magnitude\"_. From the name we can see how straightforward it is:\nit's basically put one bit (often the _MSB_) as the _sign bit_ to represent _sign_ and the remaining bits indicating\nthe magnitude (or absolute value), e.g.\n\n```cpp\n  binary   | sign-magn |  unsigned\n-----------|-----------|------------\n0 000 0000 |    +0     |     0\n0 111 1111 |    127    |    127\n...\n1 000 0000 |    -0     |    128\n1 111 1111 |   -127    |    255\n```\n\nIt was used in early computer (IBM 7090) and now mainly used in the\n_significand_ part in floating-point number\n\nPros:\n- simple and nature for human\n\nCons:\n- 2 way to represent zeros (`+0` and `-0`)\n- not as good for machine\n  - add/sub/cmp require knowing the sign\n    - complicate CPU ALU design; potentially more cycles\n\n\n### [Ones' complement](https://en.wikipedia.org/wiki/Ones%27_complement) 反码\n\nIt form a negative integers by applying a _bitwise NOT_\ni.e. _complement_ of its positive counterparts.\n\n```cpp\n  binary   |  1s comp  |  unsigned\n-----------|-----------|------------\n0000 0000  |     0     |     0\n0000 0001  |     1     |     1\n...\n0111 1111  |    127    |    127\n1000 0000  |   -127    |    128\n...\n1111 1110  |    -1     |    254\n1111 1111  |    -0     |    255\n```\n\nN.B. _MSB_ can still be signified by MSB.\n\nIt's referred to as _ones'_ complement because the negative can be formed\nby subtracting the positive **from** _ones_: `1111 1111 (-0)`\n\n```cpp\n  1111 1111       -0\n- 0111 1111       127\n---------------------\n  1000 0000      -127\n```\n\nThe benefits of the complement nature is that adding becomes simple,\nexcept we need to do an _end-around carry_ to add resulting carry\nback to get the correct result.\n\n```cpp\n  0111 1111       127\n+ 1000 0001      -126\n---------------------\n1 0000 0000        0\n          1       +1     <- add carry \"1\" back\n---------------------\n  0000 0001        1\n```\n\nPros:\n- Arithmetics on machien are fast.\n\nCons:\n- still 2 zeros!\n\n\n### [Twos' complement](https://en.wikipedia.org/wiki/Two%27s_complement) 补码\n\nMost of the current architecture adopted this, including x86, MIPS, ARM, etc.\nIt differed with one's complement by one.\n\n```cpp\n  binary   |  2s comp  |  unsigned\n-----------|-----------|------------\n0000 0000  |     0     |     0\n0000 0001  |     1     |     1\n...\n0111 1111  |    127    |    127\n1000 0000  |   -128    |    128\n1000 0001  |   -127    |    129\n...\n1111 1110  |    -2     |    254\n1111 1111  |    -1     |    255\n```\n\nN.B. _MSB_ can still be signified by MSB.\n\nIt's referred to as _twos'_ complement because the negative can be formed\nby subtracting the positive **from** `2 ** N` (congruent to `0000 0000 (+0)`),\nwhere `N` is the number of bits.\n\nE.g., for a `uint8_t`, the _sum_ of any number and it's twos' complement would\nbe `256 (1 0000 0000)`:\n\n```cpp\n1 0000 0000       256  = 2 ** 8\n- 0111 1111       127\n---------------------\n  1000 0001      -127\n```\n\nBecuase of this, arithmetics becomes really easier, for any number `x` e.g. `127`\nwe can get its twos' complement by:\n\n1. `~x => 1000 0000` bitwise NOT (like ones' complement)\n2. `+1 => 1000 0001` add 1 (the one differed from ones' complement)\n\nCons:\n- bad named?\n\nPros:\n- fast machine arithmatics.\n- only 1 zeros!\n- the minimal negative is `-128`\n\n\n### [Offset binary](https://en.wikipedia.org/wiki/Offset_binary) 移码\n\nIt's also called _excess-K_ (偏移 K) or _biased representation_, where `K` is\nthe _biasing value_ (the new `0`), e.g. in _excess-128_:\n\n```cpp\n  binary   |  K = 128  |  unsigned\n-----------|-----------|------------\n0000 0000  |   -128(-K)|     0\n0000 0001  |   -127    |     1\n...\n0111 1111  |    -1     |    127\n1000 0000  |     0     |    128  (K)\n1000 0001  |     1     |    129\n...\n1111 1111  |    127    |    255\n```\n\nIt's now mainly used for the _exponent_ part of floating-point number.\n\n\nType Conversion & `Printf`\n----------------------------------------------\n\nThis might be a little bit off topic, but I want to note down what I observed\nfrom experimenting. Basically, `printf` would not perform an implicit type\nconversion but merely _interpret_ the bits arrangement of your arguments as you\ntold it.\n\n- _UB!_ stands for _undefined behaviors_\n\n```cpp\nuint8_t u8 = 0b10000000; // 128\n int8_t s8 = 0b10000000; // -128\n\nprintf(\"%\"PRIu8 \"\\n\", u8);          // 128\nprintf(\"%\"PRId8 \"\\n\", u8);          // 128 (UB! but somehow it's got right)\nprintf(\"%\"PRId8 \"\\n\", (int8_t)u8);  // -128\n\nprintf(\"%\"PRId8 \"\\n\", s8);          // -128\nprintf(\"%\"PRIu8 \"\\n\", s8);          // 4294967168 (UB!)\nprintf(\"%\"PRId8 \"\\n\", (uint8_t)s8); // 128\n\nprintf(\"%\"PRIxPTR \"\\n\", s8);             // ffffff80\nprintf(\"%\"PRIxPTR \"\\n\", (uintptr_t)s8);  // ffffffffffffff80\n```\n\n\nChar & [ASCII](https://en.wikipedia.org/wiki/ASCII)\n-----------------\n\nTraditionally, `char` is represented in the computer as 8 bits as well. And\nreally, ASCII is only defined between `0` and `127` and require 7 bits.\n(8-bit Extended ASCII is not quite well popularized and supported.)\n\nIt's more complicated in extension such as _Unicode_ nowadays, but we'll ignore\nit for future posts dedicated for char and string representation.\n\nSo how is a `char` different with a _byte_?\n\nWell, the answer is whether a `char` is a `signed char` (backed by `int8_t`)\nor a `unsigned char` (backed by `uint8_t`) is... _implementaton-defined_.\nAnd most systems made it _signed_ since most types (e.g. `int`) were signed\nby default.\n\nN.B. `int` is standard-defined to be equivalent to `signed int`. This is\nnot the case of `char`.\n\nThat's why you often see such `typedef` such as:\n\n```cpp\ntypedef unsigned char Byte_t;\ntypedef uint8_t byte_t;\n```\n\nto emphysize the nature of byte should be just plain, unsigned, bits.\n\n\nReferences\n----------\n\n- <https://en.wikipedia.org/wiki/Integer_(computer_science)>\n- <https://www3.ntu.edu.sg/home/ehchua/programming/java/datarepresentation.html>\n"
  },
  {
    "path": "_posts/data_rep/2020-06-21-data-rep-float.md",
    "content": "---\ntitle: \"Data Representation - Floating Point Numbers\"\nsubtitle: \"「数据表示」浮点数\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - 笔记\n  - 基础\n  - C\n  - C++\n---\n\nIn the last episode we talked about the data representation of integer, a kind\nof fixed-point numbers. Today we're going to learn about floating-point numbers.\n\nFloating-point numbers are used to _approximate_ real numbers. Because of the\nfact that all the stuffs in computers are, eventually, just a limited sequence\nof bits. The representation of floating-point number had to made trade-offs\nbetween _ranges_ and _precision_.\n\nDue to its computational complexities, CPU also have a dedicated set of\ninstructions to accelerate on floating-point arithmetics.\n\n\nTerminologies\n-------------\n\nThe terminologies of floating-point number is coming from the\n[_scientific notation_](https://en.wikipedia.org/wiki/Scientific_notation),\nwhere a real number can be represented as such:\n\n```\n1.2345 = 12345 × 10 ** -4\n         -----   --    --\n  significand^   ^base  ^exponent\n```\n\n- _significand_, or _mantissa_, 有效数字, 尾数\n- _base_, or _radix_ 底数\n- _exponent_, 幂\n\nSo where is the _floating point_? It's the `.` of `1.2345`. Imaging the dot\ncan be float to the left by one to make the representation `.12345`.\n\nThe dot is called _radix point_, because to us it's seem to be a _decimal point_,\nbut it's really a _binary point_ in the computers.\n\nNow it becomes clear that, to represent a floating-point number in computers,\nwe will simply assign some bits for _significand_ and some for _exponent_, and\npotentially a bit for _sign_ and that's it.\n\n\nIEEE-754 32-bits Single-Precision Floats 单精度浮点数\n----------------------------------------\n\n- <https://en.wikipedia.org/wiki/Single-precision_floating-point_format>\n\nIt was called **single** back to IEEE-754-1985 and now **binary32** in the\nrelatively new IEEE-754-2008 standard.\n\n```cpp\n       (8 bits)             (23 bits)\nsign   exponent             fraction\n  0   011 1111 1    000 0000 0000 0000 0000 0000\n\n 31   30 .... 23    22 ....................... 0\n```\n\n- The _sign_ part took 1 bit to indicate the sign of the floats. (`0` for `+`\nand `1` for `-`. This is the same treatment as the [sign magnitute](2020-06-19-data-rep-int.md##sign-magnitude-原码).\n- The _exponent_ part took 8 bits and used [_offset-binary (biased) form_](2020-06-19-data-rep-int.md#offset-binary-移码) to represent a signed integer.\nIt's a variant form since it took out the `-127` (all 0s) for zero and `+128`\n(all 1s) for non-numbers, thus it ranges only `[-126, 127]` instead of\n`[-127, 128]`. Then, it choose the zero offset of `127` in these 254 bits (like\nusing `128` in _excess-128_), a.k.a the _exponent bias_ in the standard.\n- The _fraction_ part took 23 bits with an _implicit leading bit_ `1` and\nrepresent the actual _significand_ in total precision of 24-bits.\n\nDon't be confused by why it's called _fraction_ instead of _significand_!\nIt's all because that the 23 bits in the representation is indeed, representing\nthe fraction part of the real significand in the scientific notation.\n\nThe floating-point version of \"scientific notation\" is more like:\n\n```cpp\n(leading 1)\n   1. fraction  ×  2 ^ exponent   ×  sign\n      (base-2)           (base-2)\n```\n\nSo what number does the above bits represent?\n\n```cpp\nS     F   ×  E  =  R\n+  1.(0)  ×  0  =  1\n```\n\nAha! It's the real number `1`!\nRecall that the `E = 0b0111 1111 = 0` because it used a biased representation!\n\nWe will add more non-trivial examples later.\n\n\nDemoing Floats in C/C++\n-----------------------\n\nWriting sample code converting between binaries (in hex) and floats are not\nas straightforward as it for integers. Luckily, there are still some hacks to\nperform it:\n\n### C - Unsafe Cast\n\nWe unsafely cast a pointer to enable reinterpretation of the same binaries.\n\n```cpp\nfloat f1 = 0x3f800000; // C doesn't have a floating literal taking hex.\nprintf(\"%f \\n\", f1);   // 1065353216.000000 (???)\n\nuint32_t u2 = 0x3f800000;\nfloat* f2 = (float*)&u2;   // unsafe cast\nprintf(\"%f \\n\", *f2);      // 1.000000\n```\n\n### C - Union Trick\n\nOh I really enjoyed this one...Union in C is not only untagged union, but also\nshare the exact same chunk of memory. So we are doing the same reinterpretation,\nbut in a more structural and technically fancier way.\n\n```cpp\n#include <stdint.h>\n#include <inttypes.h>\n#include <math.h>\n\nfloat pi = (float)M_PI;\nunion {\n    float f;\n    uint32_t u;\n} f2u = { .f = pi };  // we took the data as float\n\nprintf (\"pi : %f\\n   : 0x%\" PRIx32 \"\\n\", pi, f2u.u);  // but interpret as uint32_t\npi : 3.141593\n   : 0x40490fdb\n```\n\nN.B. this trick is well-known as [type punning](https://en.wikipedia.org/wiki/Type_punning):\n\n> In computer science, type punning is a common term for any programming technique that subverts or circumvents the type system of a programming language in order to achieve an effect that would be difficult or impossible to achieve within the bounds of the formal language.\n\n### C++ - `reinterpret_cast`\n\nC++ does provide such type punning to the standard language:\n\n```cpp\nuint32_t u = 0x40490fdb;\nfloat a = *reinterpret_cast<float*>(&u);\nstd::cout << a;  // 3.14159\n```\n\nN.B. it still need to be a conversion between pointers,\nsee <https://en.cppreference.com/w/cpp/language/reinterpret_cast>.\n\nBesides, C++ 17 does add a floating point literal that can take hex, but it\nworks in a different way, using an explicit radix point in the hex:\n\n```cpp\nfloat f = 0x1.2p3;  // 1.2 by 2^3\nstd::cout << f;     // 9\n```\n\nThat's try with another direction:\n\n```cpp\n#include <iostream>\n#include <stdint.h>\n#include <inttypes.h>\n\nint main() {\n  double qNan = std::numeric_limits<double>::quiet_NaN();\n  printf(\"0x%\" PRIx64 \"\\n\", *reinterpret_cast<uint64_t*>(&qNan));\n  // 0x7ff8000000000000, the canonical qNaN!\n}\n```\n\n\nRepresentation of Non-Numbers\n-----------------------------\n\nThere are more in the IEEE-754!\n\nReal numbers doesn't satisfy [closure property](https://en.wikipedia.org/wiki/Closure_(mathematics))\nas integers does. Notably, the set of real numbers is NOT closed under the\ndivision! It could produce non-number results such as **infinity** (e.g. `1/0`)\nand [**NaN (Not-a-Number)**](https://en.wikipedia.org/wiki/NaN) (e.g. taking\na square root of a negative number).\n\nIt would be algebraically ideal if the set of floating-point numbers can be\nclosed under all floating-point arithmetics. That would made many people's life\neasier. So the IEEE made it so! Non-numeber values are squeezed in.\n\nWe will also include the two zeros (`+0`/`-0`) into the comparison here,\nsince they are also special by being the only two demanding an `0x00` exponent:\n\n```cpp\n             binary                |    hex    |\n--------------------------------------------------------\n0 00000000 00000000000000000000000 = 0000 0000 = +0\n1 00000000 00000000000000000000000 = 8000 0000 = −0\n\n0 11111111 00000000000000000000000 = 7f80 0000 = +infinity\n1 11111111 00000000000000000000000 = ff80 0000 = −infinity\n\n_ 11111111 10000000000000000000000 = _fc0 0000 = qNaN (canonical)\n_ 11111111 00000000000000000000001 = _f80 0001 = sNaN (one of them)\n```\n\n```cpp\n      (8 bits)  (23 bits)\nsign  exponent  fraction\n  0      00     0 ...0 0  = +0\n  1      00     0 ...0 0  = -0\n  0      FF     0 ...0 0  = +infinity\n  1      FF     0 ...0 0  = -infinity\n  _      FF     1 ...0 0  = qNaN (canonical)\n  _      FF     0 ...0 1  = sNaN (one of them)\n```\n\nEncodings of qNaN and sNaN are not specified in IEEE 754 and implemented\ndifferently on different processors. Luckily, both x86 and ARM family use the\n\"most significant bit of fraction\" to indicate whether it's quite.\n\n### More on NaN\n\nIf we look carefully into the IEEE 754-2008 spec, in the _page35, 6.2.1_, it\nactually defined anything with exponent `FF` and not a infinity (i.e. with\nall the fraction bits being `0`), a NaN!\n\n> All binary NaN bit strings have all the bits of the biased exponent field E set to 1 (see 3.4). A quiet NaN bit string should be encoded with the first bit (d1) of the trailing significand field T being 1. A signaling NaN bit string should be encoded with the first bit of the trailing significand field being 0.\n\nThat implies, we actually had `2 ** 24 - 2` of NaNs in a 32-bits float!\nThe `24` came from the `1` sign bit plus `23` fractions and the `2` excluded\nwere the `+/- inf`.\n\nThe continuous 22 bits inside the fraction looks quite a waste, and there\nwould be even 51 bits of them in the `double`! We will see how to made them useful\nin later episodes (spoiler: they are known as _NaN payload_).\n\nIt's also worth noting that it's weird that the IEEE choose to use the MSB\ninstead of the sign bit for NaN quiteness/signalness:\n\n> It seems strange to me that the bit which signifies whether or not the NaN is signaling is the top bit of the mantissa rather than the sign bit; perhaps something about how floating point pipelines are implemented makes it less natural to use the sign bit to decide whether or not to raise a signal.\n> -- <https://anniecherkaev.com/the-secret-life-of-nan>\n\nI guess it might be something related to the CPU pipeline? I don't know yet.\n\n\n### Equality of NaNs and Zeros.\n\nThe spec defined a comparison with NaNs to return an **unordered result**, that\nmeans any comparison operation except `!=`, i.e. `>=, <=, >, <, =` between a\nNaN and any other floating-point number would return `false`.\n\nNo surprised that most (if not every) language implemented such behaviours, e.g.\nin JavaScript:\n\n```js\nNaN !== NaN   // true\nNaN === NaN   // false\nNaN >  1      // false\nNaN <  1      // false\n```\n\nPosition and negative zeros, however, are defined to be equal!\n\n```js\n+0 === -0  // true, using the traditional JS equality\nObject.is(+0, -0)  // false, using the \"SameValue\" equality\n```\n\nIn Cpp, we can tell them apart by looking at its sign bit:\n\n```cpp\n#include <cmath>   // signbit\n\ncout << (+0.0f == -0.0f);     // 1\ncout << std::signbit(-0.0f);  // 1\ncout << std::signbit(+0.0f);  // 0\n```\n\n\n\n\nIEEE-754 64-bits Double-Precision Floats\n----------------------------------------\n\n- <https://en.wikipedia.org/wiki/Double-precision_floating-point_format>\n\nNow, the 64-bit versions floating-point number, known as `double`, is just a\nmatter of scale:\n\n```cpp\n       (11 bits)            (52 bits)\nsign   exponent             fraction\n  0\n\n 63   62 .... 52    51 ....................... 0\n```\n\n\nIEEE-754-2008 16-bits Short Floats\n----------------------------------------\n\nThe 2008 edition of IEEE-754 also standardize the `short float`, which is\nneither in C or C++ standard. Though compiler extension might include it.\n\nIt looks like:\n\n```cpp\n1 sign bit | 5 exponent bits | 10 fraction bits\nS            E E E E E         M M M M M M M M M M\n```\n\n\n\nReferences\n----------\n\n- <https://en.wikipedia.org/wiki/Floating-point_arithmetic>\n- <https://www3.ntu.edu.sg/home/ehchua/programming/java/datarepresentation.html>\n"
  },
  {
    "path": "_posts/data_rep/2020-06-21-data-rep-todo.md",
    "content": "---\ntitle: \"Data Representation - TODO\"\nsubtitle: \"「数据表示」待写\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\npublished: false\ntags:\n  - 笔记\n  - 基础\n  - C\n  - C++\n---\n\n- Endianness\n- String (Char Sequence e.g. NULL `0x00`)\n- Unicode / UTF8\n- Struct and Alignment\n- Tagging\n  - Tagged Pointer\n  - NaN tagging\n  - Tagged Integer (SMI)\n"
  },
  {
    "path": "_posts/hidden/2020-05-05-pl-chart.md",
    "content": "---\ntitle: \"My Programming Languages Spectrum\"\nsubtitle: \"我的编程语言光谱\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\nplchart: true\ntags:\n---\n\n<iframe \n  id=\"chart\"\n  src=\"https://huangxuan.me/PL-chart/\"\n  frameborder=\"0\" \n  scrolling=\"no\" \n  style=\"width: 100%\">\n</iframe>\n"
  },
  {
    "path": "_posts/read_sf_lf/2019-01-01-sf-lf-01-basics.md",
    "content": "---\ntitle: \"「SF-LC」1 Basics\"\nsubtitle: \"Logical Foundations - Functional Programming in Coq\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - LF (逻辑基础)\n  - SF (软件基础)\n  - Coq\n  - 笔记\n---\n\n> These series of notes combined \n> - My notes on reading _Software Foundation_ and (if any) watching on _Coq intensive_.\n> - Gotchas from my independent studies and discussion within _Prof.Fluet_'s class.\n\n> The `.v` code is a gorgeous example of _literal programming_ and the compiled `.html` website is full-fledged.\n> So this note is intended to be NOT self-contained and only focus on things I found essential or interesting.\n\n> This note is intended to be very personal and potentially mix English with Chinese (You can Lol)\n> So yeah. Don't expect it to be well organized and well written.\n> I posted it on blog mainly for my own references purpose.\n\n> The quotes could either come from the book or saying from someone (even including me). \n\n\nData and Functions\n------------------\n\n### Custom Notation\n\n```coq\nNotation \"x && y\" := (andb x y).\nNotation \"x || y\" := (orb x y).\n```\n\ncan go pretty far with unicode char...\n\nmaking things _infix_\n\n```coq\nNotation \"x + y\" := (plus x y)\n                      (at level 50, left associativity)\n                      : nat_scope.\nNotation \"x - y\" := (minus x y)\n                      (at level 50, left associativity)\n                      : nat_scope.\nNotation \"x * y\" := (mult x y)\n                      (at level 40, left associativity)\n                      : nat_scope.\n```\n\nwhy `40` `50`? Making sure there are still _rooms_ for priority in between...\n\n> no known PL using real number for priority though\n\n\n\n### Data Constructor with arguments\n\nthere are 2 ways to define them:\n\n```coq\nInductive color : Type :=\n  | black\n  | white\n  | primary (p : rgb).      (* ADT, need to name arg, useful in proof *)\n  | primary : rgb -> color. (* GADT style, dependent type *)\n```\n\n\n\n### Syntax for arguments having the same type\n\n\n> As a notational convenience, if two or more arguments have the same type, they can be written together\n\n```coq\nInductive nybble : Type :=\n  | bits (b0 b1 b2 b3 : bit).\n```\n\n```coq\nFixpoint mult (n m : nat)         : nat := \nFixpoint plus (n : nat) (m : nat) : nat := \n```\n\n\n`Fixpoint` and Structrual Recursion\n-----------------------------------\n\n> This requirement is a fundamental feature of Coq's design: In particular, it guarantees that every function that can be defined in Coq will terminate on all inputs.\n\nHowever, Coq's \"decreasing analysis\" is not very sophisticated. E.g.\n\n```coq\nFixpoint evenb (n:nat) : bool :=\n  match n with\n  | O        => true\n  | S O      => false\n  | n        => evenb (pred (pred n))\n  end.\n```\n\nwill result in a error that basically complains _\"this structure is not shrinking\"_.\n\n```\nError:\nRecursive definition of evenb is ill-formed.\n\nevenb : nat -> bool\nn : nat\nn0 : nat\nn1 : nat\n\nRecursive call to evenb has principal argument equal to\n\"Nat.pred (Nat.pred n)\" instead of one of the following variables: \"n0\" \"n1\".\n\nRecursive definition is:\n\"fun n : nat =>\n match n with\n | 0 => true\n | 1 => false\n | S (S _) => evenb (Nat.pred (Nat.pred n))\n end\".\n```\n\nN.B. the `n0` and `n1` are sub-terms of `n` where `n = S (S _)`.\n\nSo we have to make the sub-structure explicit to indicate the structure is obviously shrinking:\n\n```coq\nFixpoint evenb (n:nat) : bool :=\n  match n with\n  | O        => true\n  | S O      => false\n  | S (S n') => evenb n'\n  end.\n```\n\nNow Coq will know this `Fixpoint` is performing a structural recursion over the 1st recursion and it guarantees to be terminated since the structure is decreasing:\n\n```\nevenb is defined\nevenb is recursively defined (decreasing on 1st argument)\n```\n\n\nProof by Case Analysis\n----------------------\n\n```coq\nTheorem plus_1_neq_0_firsttry : ∀n : nat,\n  (n + 1) =? 0 = false.\nProof.\n  intros n.\n  simpl. (* does nothing! *)\nAbort.\n```\n\n`simpl.` does nothing since both `+` and `=?` have 2 cases.\n\nso we have to `destruct` `n` as 2 cases: nullary `O` and unary `S n'`.\n\n```coq\nintros n. destruct n as [ (* O *) | (* S *) n'] eqn:E.\n```\n\n- the _intro pattern_ `as [ |n']` name new bindings.\n- `eqn:E` annonate the destructed `eqn` (equation?) as `E` in the premises of proofs. It could be elided if not explicitly used, but useful to keep for the sake of documentation as well.\n\n```coq\nsubgoal 1\n\n  n : nat\n  E : n = 0                          (* case 1, n is [O] a.k.a. [0] *)\n  ============================\n  (0 + 1 =? 0) = false\n\n\nsubgoal 2\n\n  n, n' : nat\n  E : n = S n'                       (* case 2, n is [S n'] *)\n  ============================\n  (S n' + 1 =? 0) = false\n```\n\nIf there is no need to specify any names, we could omit `as` clause or simply write `as [|]` or `as []`.\nIn fact. Any `as` clause could be ommited and Coq will fill in random var name auto-magically.\n\n\n### A small caveat on `intro` \n\n\n```coq\nintros x y. destruct y as [ | y ] eqn:E.\n```\n\nBy doing this, name `y` is shadowed. It'd usually better to use, say `y'` for this purpose.\n\n\n\n`Qed`\n-----\n\nstanding for Latin words _\"Quod Erat Demonstrandum\"_...meaning \"that which was to be demonstrated\".\n"
  },
  {
    "path": "_posts/read_sf_lf/2019-01-02-sf-lf-02-induction.md",
    "content": "---\ntitle: \"「SF-LC」2 Induction\"\nsubtitle: \"Logical Foundations - Proof by Induction\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - LF (逻辑基础)\n  - SF (软件基础)\n  - Coq\n  - 笔记\n---\n\n## Review (only in slide)\n\n```coq\nTheorem review2: ∀b, (orb true b) = true.\nTheorem review3: ∀b, (orb b true) = true.\n```\n\nWhether or not it can be just `simpl.` depending on the definition of `orb`.\n\nIn _Proof Engineering_, we probably won't need to include `review2` but need to include `review3` in library.\n\n> Why we have `simpl.` but not `refl.` ?\n\n\nProving `0` is a \"neutral element\" for `+` (additive identity)\n--------------------------------------------------------------\n\n### Proving `0 + n = n`\n\n```coq\nTheorem plus_O_n : forall n : nat, 0 + n = n.\nProof.\n  intros n. simpl. reflexivity. Qed.\n```\n\nThis can be simply proved by _simplication_ bcuz the definition of `+` is defined by pattern matching against 1st operand:\n\n```coq\nFixpoint plus (n : nat) (m : nat) : nat :=\n  match n with\n    | O ⇒ m\n    | S n' ⇒ S (plus n' m)\n  end.\n```\n\nWe can observe that if `n` is `0`(`O`), no matter `m` is, it returns `m` as is.\n\n\n### Proving `n + 0 = n`\n\n#### 1st try: Simplication\n\n```coq\nTheorem plus_O_n_1 : forall n : nat,  n + 0 = n.\nProof.\n  intros n.\n  simpl. (* Does nothing! *)\nAbort.\n```\n\nThis cannot be proved by _simplication_ bcuz `n` is unknown so _unfold_ the definition `+` won't be able to simplify anything.\n\n#### 2nd try: Case Analysis\n\n```coq\nTheorem plus_n_O_2 : ∀n:nat,\n  n = n + 0.\nProof.\n  intros n. destruct n as [| n'] eqn:E.\n  - (* n = 0 *)\n    reflexivity. (* so far so good... *)\n  - (* n = S n' *)\n    simpl. (* ...but here we are stuck again *)\nAbort.\n```\n\nOur 2nd try is to use _case analysis_ (`destruct`), but the proof stucks in _inductive case_ since `n` can be infinitely large (destructed)\n\n\n#### Induction to the resucue\n\n> To prove interesting facts about numbers, lists, and other inductively defined sets, we usually need a more powerful reasoning principle: induction.\n\nPrinceple of induction over natural numbers (i.e. _mathematical induction_)\n\n```coq\nP(0); ∀n' P(n') → P(S n')  ====>  P(n)\n```\n\nIn Coq, like `destruct`, `induction` break `P(n)` into 2 subgoals:\n\n```coq\nTheorem plus_n_O : ∀n:nat, n = n + 0.\nProof.\n  intros n. induction n as [| n' IHn'].\n  - (* n = 0 *) reflexivity.\n  - (* n = S n' *) simpl. rewrite <- IHn'. reflexivity. Qed.\n```\n\n\nProving `n - n = 0`\n-------------------\n\n```coq\nTheorem minus_diag : ∀n,\n  minus n n = 0.\nProof.\n  (* WORKED IN CLASS *)\n  intros n. induction n as [| n' IHn'].\n  - (* n = 0 *)\n    simpl. reflexivity.\n  - (* n = S n' *)\n    simpl. rewrite → IHn'. reflexivity. Qed\n```\n\nNoticed that the definition of `minus`:\n\n```coq\n    Fixpoint minus (n m:nat) : nat :=\n      match n, m with\n      | O   , _    => O\n      | S _ , O    => n\n      | S n', S m' => minus n' m'\n      end.\n```\n\n`rewrite`\n---------\n\n`rewrite` would do a (DFS) preorder traversal in the syntax tree.\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "_posts/read_sf_lf/2019-01-03-sf-lf-03-list.md",
    "content": "---\ntitle: \"「SF-LC」3 List\"\nsubtitle: \"Logical Foundations - Working with Structured Data\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - LF (逻辑基础)\n  - SF (软件基础)\n  - Coq\n  - 笔记\n---\n\nPair of Numbers\n---------------\n\nQ: Why name `inductive`? \nA: Inductive means _building things bottom-up_, it doesn't have to self-referencial (recursive)\n(see below `induction on lists` as well.)\n\n```coq\nInductive natprod : Type :=\n  | pair (n1 n2 : nat).\n\nNotation \"( x , y )\" := (pair x y).\n```\n\nProof on pair cannot simply `simpl.`\n\n```coq\nTheorem surjective_pairing_stuck : ∀(p : natprod),\n  p = (fst p, snd p).\nProof.\n  simpl. (* Doesn't reduce anything! *)\nAbort.\n```\n\nWe have to _expose the structure_:\n\n```coq\nTheorem surjective_pairing : ∀(p : natprod),\n  p = (fst p, snd p).\nProof.\n  intros p. destruct p as [n m**. simpl. reflexivity. Qed.\n```\n\nIt only generate **one subgoal**, becasue\n> That's because natprods can only be constructed in one way.\n\n\n### My take on `destruct`\n\n`destruct` \n\n* destruct `bool` to `true` and `false`\n* destruct `nat`  to `O` and `S n'` (inductively defined)\n* destruct `pair` to `(n, m)`\n\nThe **prove by case analysis (exhaustive)** is just an application of the idea of _destruction_! \n\nthe idea simply _destruct_ the data type into its data constructors (representing ways of constructing this data)\n\n- Java class has only 1 way to construct (via its constructor)\n- Scala case class then have multiple way to construct\n\n\nLists of Numbers\n----------------\n\n> Generalizing the definition of pairs\n\n```coq\nInductive natlist : Type :=\n  | nil\n  | cons (n : nat) (l : natlist).\n```\n\nThe ability of quosiquotation using `Notation` is awesome:\n\n```coq\nNotation \"x :: l\" := (cons x l) (at level 60, right associativity).\nNotation \"[ ]\" := nil.\nNotation \"[ x ; .. ; y ]\" := (cons x .. (cons y nil) ..).\n```\n\nIt's exactly like OCaml, even for `;`, `at level 60` means it's tightly than `+ at level 50` .\n\n```coq\nNotation \"x ++ y\" := (app x y) (right associativity, at level 60).\n```\n    \nInstead of SML/OCaml's `@`, Coq chooses Haskell's `++`.\n\n\n### `hd` with default\n\nCoq function (for some reason) has to be **total**, so `hd` require a `default` value as 1st argument:\n\n```coq\nDefinition hd (default:nat) (l:natlist) : nat :=\n  match l with\n  | nil ⇒ default\n  | h :: t ⇒ h\n  end.\n```\n\n\nInduction on Lists.\n-------------------\n\nThe definition of _inductive defined set_\n\n> Each Inductive declaration defines a set of data values that can be **built up** using the declared constructors:\n> - a boolean can be either true or false;\n> - a number can be either O or S applied to another number; \n> - a list can be either nil or cons applied to a number and a list.\n\nThe reverse: reasoning _inductive defined sets_\n\n> Moreover, applications of the declared constructors to one another are the \n> **only** possible shapes that elements of an inductively defined set can have,\n> and this fact directly gives rise to a way of reasoning about inductively defined sets: \n> - a number is either O or else it is S applied to some smaller number; \n> - a list is either nil or else it is cons applied to some number and some smaller list;\n\nReasoning lists\n\n> if we have in mind some proposition `P` that mentions a list `l` and we want to argue that `P` holds for *all* lists, \n> we can reason as follows\n> 1. First, show that `P` is `true` of `l` when `l` is `nil`.\n> 2. Then show that `P` is true of `l` when `l` is `cons n l'` for some number `n` and some smaller list `l'`, assuming that `P` is `true` for `l'`. \n\n\nSearch\n------\n\n```coq\nSearch rev  (* list all theorems of [rev] *)\n```\n\n\nCoq Conditionals (`if then else`)\n---------------------------------\n\n```coq\nFixpoint nth_error' (l:natlist) (n:nat) : natoption :=\n  match l with\n  | nil ⇒ None\n  | a :: l' ⇒ if n =? O then Some a\n                        else nth_error' l' (pred n)\n  end.\n```\n\nOne small generalization: since the boolean type in Coq is not built-in. Coq actually supports conditional expr **over any** _inductive defined typewith two constructors_. First constructor is considered true and false for second.\n\n\n\n\n\nStuck in Proof\n--------------\n\ncould be many cases\n\n* wrong tactics\n* wrong theroem!! (might derive to counterexample)\n* wrong step (most hard to figure out)\n  * induction on wrong things\n"
  },
  {
    "path": "_posts/read_sf_lf/2019-01-04-sf-lf-04-poly.md",
    "content": "---\ntitle: \"「SF-LC」4 Poly\"\nsubtitle: \"Logical Foundations - Polymorphism and Higher-Order Functions\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - LF (逻辑基础)\n  - SF (软件基础)\n  - Coq\n  - 笔记\n---\n\n> The critical new ideas are \n> polymorphism (abstracting functions over the types of the data they manipulate) and \n> higher-order functions (treating functions as data). \n\n\n## Polymorphism\n\nUntil today, We were living in the monomorphic world of Coq. \nSo if we want a list, we have to define it for each type:\n\n```coq\nInductive boollist : Type :=\n  | bool_nil\n  | bool_cons (b : bool) (l : boollist).\n```\n\n\n## Polymorphic Type and Constructors\n\nBut of course Coq supports polymorphic type.\nSo we can _abstract things over type_\n\n```coq\nInductive list (X:Type) : Type :=\n  | nil\n  | cons (x : X) (l : list X).\n\nCheck list.\n(* ===> list : Type -> Type *)\n```\n\nRecall from PLT course, this is exacly **Parametric Polymorphism** \nand it's **SystemFω**. the `list` here is a type-level small lambda, or **type operators**\n\nAnother things I'd love to mention is the concrete syntax of `list X`,\nit didn't choose the SML/OCaml order but the Haskell order. \n\n\n### Q1. What's the type of `nil` and `cons`?\n\nBoth having `forall` type\n\n```coq\nCheck nil.\n(* ===> nil : forall X : Type, list X *)\nCheck cons.\n(* ===> nil : forall X : Type, X -> list X -> list X *)\n```\n\n\n### Q2. What's the type of `list nat`? Why not `Type` but weird `Set`?\n\n```coq\nCheck nat.\n(* ===> nat : Set *)\nCheck list nat.\n(* ===> list nat : Set *)\nCheck Set.\n(* ===> Set: Type *)\nCheck Type.\n(* ===> Type: Type *)\n```\n\n```coq\nCheck (cons nat 2 (cons nat 1 (nil nat))).\n```\n\n\n## Polymorphic Functions\n\nwe can make polymorphic versions of list-processing function:\n\nBtw, Pierce follows the TAPL convention where type is written in capital letter but not greek letter, \nless clear in first look but better for typing in real programming.\n\n```coq\nFixpoint repeat (X : Type) (x : X) (count : nat) : list X :=\n  match count with\n  | 0 ⇒ nil X\n  | S count' ⇒ cons X x (repeat X x count')\n  end.\n```\n\nThis is *SystemF*.\n\n```\nCheck repeat.\n(* ===> repeat : forall X : Type, X -> nat -> list X *)\n```\n\n\n## Slide QA\n\n1. ill-typed\n2. `forall X : Type, X -> nat -> list X`\n3. `list nat`\n\n\n\n## Type Argument Inference\n\n`X` must be a `Type` since `nil` expects an `Type` as its first argument.\n\n```coq\nFixpoint repeat' X x count : list X :=     (* return type [:list X] can be omitted as well *)\n  match count with\n  | 0 ⇒ nil X\n  | S count' ⇒ cons X x (repeat' X x count')\n  end.\n\nCheck repeat'.\n(* ===> forall X : Type, X -> nat -> list X *)\n```\n\n\n## Type Argument Synthesis\n\nWe can write `_` (hole) in place of `X` and Coq will try to **unify** all local information.\n\n```coq\nFixpoint repeat'' X x count : list X :=\n  match count with\n  | 0 ⇒ nil _\n  | S count' ⇒ cons _ x (repeat'' _ x count')\n  end.\n\nDefinition list123' :=\n  cons _ 1 (cons _ 2 (cons _ 3 (nil _))).\n```\n\nSame underlying mechanisms:\n\n```coq\nrepeat' X x count : list X :=\nrepeat' (X : _) (x : _) (count : _) : list X :=\n```\n\n\n## Implicit Arguments\n\nUsing `Arguments` directives to tell if an argument need to be implicit (i.e. omitted and always to infer) or not. \n\n> Implicitly convert to `_` (synthesis) by frontend.\n\n```coq\nArguments nil {X}.\nArguments cons {X} _ _.       (* data constructor usually don't specify the name *)\nArguments repeat {X} x count. (* fun definition usually do *)\n```\n\nThe even more convenient syntax is that we can declare them right in our function definition.\nJust _surrounding them with curly braces_.\n\n```coq\nFixpoint repeat''' {X : Type} (x : X) (count : nat) : list X :=\n  match count with\n  | 0 ⇒ nil\n  | S count' ⇒ cons x (repeat''' x count')\n  end.\n```\n\n\n## Implicit Arguments Pitfalls on `Inductive`\n\n```coq\nInductive list' {X:Type} : Type :=\n  | nil'\n  | cons' (x : X) (l : list').\n```\n\nDoing this will make `X` implicit for even `list'`, the type constructor itself... \n\n\n## Other Polymorphic List functions\n\nNo difference but add implicit type argument `{X : Type}`.\n\n\n## Supplying Type Arguments Explicitly\n\n```coq\nFail Definition mynil := nil.\n\nDefinition mynil : list nat := nil.\n\nCheck @nil. (* ===> @nil : forall X : Type, list X *)\nDefinition mynil' := @nil nat.\n```\n\nFirst thought: Existential\nSecond thought: A wait to be unified Universal. (after being implicit and require inference)\n\n```coq\nCheck nil.\n\nnil : \n   list ?X\nwhere ?X : [ |- Type]\n```\n\n\n## List notation\n\n```coq\nNotation \"x :: y\" := (cons x y)\n                     (at level 60, right associativity).\nNotation \"[ ]\" := nil.\nNotation \"[ x ; .. ; y ]\" := (cons x .. (cons y []) ..).\nNotation \"x ++ y\" := (app x y)\n                     (at level 60, right associativity).\n```\n\nSame with before thanks to the implicit argument\n\n\n\n## Slide Q&A 2\n\n\n1. we use `;` not `,`!!\n2. `list nat`\n3. ill-typed\n4. ill-typed\n5. `list (list nat)`\n6. `list (list nat)` (tricky in first look)\n7. `list bool`\n8. ill-typed\n9. ill-typed\n\n\n\n## Poly Pair\n\n```coq\nInductive prod (X Y : Type) : Type :=\n| pair (x : X) (y : Y).\nArguments pair {X} {Y} _ _.  (* omit two type var **)\n\nNotation \"( x , y )\" := (pair x y).\nNotation \"X * Y\" := (prod X Y) : type_scope.  (* only be used when parsing type, avoids clashing with multiplication *)\n```\n\nBe careful of `(X,Y)` and `X*Y`. Coq pick the ML way, not haskell way.\n\n\n\n## `Combine` or `Zip`\n\n```coq\nFixpoint combine {X Y : Type} (lx : list X) (ly : list Y)\n           : list (X*Y) :=\n  match lx, ly with\n  | [], _ ⇒ []\n  | _, [] ⇒ []\n  | x :: tx, y :: ty ⇒ (x, y) :: (combine tx ty)\n  end.\n```\n\nGuess type?\n\n```coq\nCheck @combine.\n@combine\n     : forall X Y : Type,\n       list X -> list Y -> list (X * Y)\n\n(* A special form of `forall`? *)\nCheck combine.\ncombine\n     : list ?X -> list ?Y -> list (?X * ?Y)\nwhere\n?X : [ |- Type]\n?Y : [ |- Type]\n```\n\n\n## Poly Option\n\n```coq\nInductive option (X:Type) : Type :=\n  | Some (x : X)\n  | None.\n\nArguments Some {X} _.\nArguments None {X}.\n\n\n(* find nth element if exist, None otherwise *)\nFixpoint nth_error {X : Type} (l : list X) (n : nat) : option X :=\n  match l with\n  | [] ⇒ None\n  | a :: l' ⇒ if n =? O then Some a else nth_error l' (pred n)\n  end.\n```\n\n\n## Function as data\n\n_Functions as first-class citizens_\n\n\n## Higher-Order Functions\n\n```coq\nDefinition doit3times {X:Type} (f:X→X) (n:X) : X :=\n  f (f (f n)).\n\nCheck @doit3times.\n(* ===> doit3times : forall X : Type, (X -> X) -> X -> X *)\n```\n\n\n## Filter (taking a _predicate_ on `X`)\n\n_collection-oriented_ programming style - my first time seeing this, any comments?\n\n```coq\nFixpoint filter {X:Type} (test: X→bool) (l:list X)\n                : (list X) :=\n  match l with\n  | [] ⇒ []\n  | h :: t ⇒ if test h then h :: (filter test t)\n                        else filter test t\n  end.\n\nExample test_filter1: filter evenb [1;2;3;4] = [2;4].\nProof. reflexivity. Qed.\n```\n\n\n## Anonymous Functions\n\n> It is arguably a little sad, in the example just above, to be forced to define the function length_is_1 and give it a name just to be able to pass it as an argument to filter\n\n```coq\nExample test_anon_fun':\n  doit3times (fun n ⇒ n * n) 2 = 256.\nProof. reflexivity. Qed.\n```\n\nSyntax: hybrid of OCaml `fun n -> n` and SML `fn n => n`.\nand support multi-arguments (curried)\n\n```coq\nCompute ((fun x y => x + y) 3 5).\n```\n\n\n## Map\n\nShould be familar.\n\n```coq\nFixpoint map {X Y: Type} (f:X→Y) (l:list X) : (list Y) :=\n  match l with\n  | [] ⇒ []\n  | h :: t ⇒ (f h) :: (map f t)\n  end.\n```\n\n```coq\nCheck @map\n\n@map : forall X Y : Type, (X -> Y) -> list X -> list Y\n```\n\n\n## Slide Q&A 3\n\n1. as above\n\n\n\n## `option` map\n\n```coq\nDefinition option_map {X Y : Type} (f : X → Y) (xo : option X) : option Y :=\n  match xo with\n    | None ⇒ None\n    | Some x ⇒ Some (f x)\n  end.\n```\n\nFunctor Map (`fmap`) !\n\n\n## Fold (Reduce)\n\n```coq\nFixpoint fold {X Y: Type} (f: X→Y→Y) (l: list X) (b: Y) : Y :=\n  match l with\n  | nil ⇒ b\n  | h :: t ⇒ f h (fold f t b)\n  end.\n```\n\nFold Right (`foldr`). Argument order same with OCaml, different with Haskell. \n\n```coq\nCheck @fold\n\n@fold\n     : forall X Y : Type,\n       (X -> Y -> Y) -> list X -> Y -> Y\n```\n\n## Slide Q&A 4\n\n1. as above (type can be simply readed out)\n2. `list nat -> nat -> nat`\n3. 10\n\n\n## Functions That Construct Functions\n\nShould be familar.\nUse of _closure_.\n\n```coq\ndefinition constfun {X: Type} (x: X) : nat→X :=\n  fun (k:nat) ⇒ x.\n\nDefinition ftrue := constfun true.\nExample constfun_example1 : ftrue 0 = true.\n\nExample constfun_example2 : (constfun 5) 99 = 5.\n```\n\n**Curried** and **partial application**\n\n```coq\nCheck plus.\n(* ==> nat -> nat -> nat *)\n\nCheck plus 3.\n(* ==> nat -> nat *)\n```\n\n\n## Universe Inconsistency\n\nI encounter this problem when doing church numeral exercise.\n\n```coq\nDefinition plus (n m : cnat) : cnat := n cnat succ m.\n```\n\nwill result in `universe inconsistency` error.\n\n\n```coq\nSet Printing Universes. (* giving more error msg *)\n\nIn environment\nn : cnat\nm : cnat\nThe term \"cnat\" has type \"Type@{Top.168+1}\" while it is expected to have type \"Type@{Top.168}\"\n(universe inconsistency: Cannot enforce Top.168 < Top.168 because Top.168 = Top.168).\n```\n\n\n### What's happening?\n\n> Yes, you can define: `Definition plus (n m : cnat) : cnat := n cnat succ m.` in System F.  However, in Coq's richer logic, you need to be a little more careful about allowing types to be instantiated at their own types, else you run into issue of inconsistency. Essentially, there is a stratification of types (by \"universes\") that says that one universe cannot contain a \"bigger\" universe. Often, things are polymorphic in their universe (i.e., work in all universes), you run into this where you cannot instantiate the \"forall X, ...\" that is the definition of cnat by cnat itself.\n> -- Prof. Fluet\n\n\n### <https://stackoverflow.com/questions/32153710/what-does-error-universe-inconsistency-mean-in-coq>\n\n`Check Type => Type` is a bit of a lie, everytime it the `Type` is not that same, but __a bigger one__.\n\n> Formally, every Type has an index associated to it, called its _universe level_.\n\n```coq\nSet Printing Universes. (* giving more error msg *)\n\nCheck Type. \nType@{Top.1} : Type@{Top.1+1} (* {Top.1} |=  *)\n\nCheck Type. \nType@{Top.2} : Type@{Top.2+1} (* {Top.2} |=  *)\n```\n\n> Thus, the correct answer for that question is that `Type_i` has type `Type_j`, for any index `j > i`. This is needed to ensure the consistency of Coq's theory: _if there were only one Type, it would be possible to show a contradiction, similarly to how one gets a contradiction in set theory if you assume that there is a set of all sets._\n> Coq generates one new index variable every time you write Type, and keeps track of internal constraints\n\n> The error message you saw means that _Coq's constraint solver_ for universe levels says that there can't be a solution to the constraint system you asked for.\n\n> The problem is that the `forall` in the definition of `nat` is quantified over `Type_i`, but Coq's logic forces `nat` to be itself of type `Type_j`, with `j > i`. On the other hand, the application `n nat` requires that `j <= i`, resulting in a non-satisfiable set of index constraints.\n\nFrom my understanding, the essences are: \n\n1. reasons: Allowing self-application introduces _logic contradiction (paradox)_.\n2. understanding: The `forall` is quantified over _types in the previous universe_ (the universe w/o itself).\n\n\n### From <https://coq.inria.fr/refman/addendum/universe-polymorphism.html>\n\n```coq\nDefinition identity {A : Type} (a : A) := a.\n\nFail Definition selfid := identity (@identity).\n```\n\n```coq\nThe command has indeed failed with message:\nThe term \"@identity\" has type \"forall A : Type, A -> A\"\nwhile it is expected to have type \"?A\"\n(unable to find a well-typed instantiation for \"?A\": cannot ensure that\n\"Type@{Top.1+1}\" is a subtype of \"Type@{Top.1}\").\n```\n\nThe link also introduce some advanced/experimental way to do _polymorphic universe_\n\n\n\n## Polymorphic Church Numerals w/o self-applying itself\n\n> References: <https://en.wikipedia.org/wiki/Church_encoding>\n\n### Definition\n\nUntyped doesn't need to declare type...\nSTLC doesn't have enough expressive power to represent church encoding\nSystem F definition: \n\n```coq\nDefinition cnat := forall X : Type, (X -> X) -> X -> X.\n```\n\n### `succ`\n\n```haskell\nsucc = \\n s z -> s (n s z) \n```\n\n```coq\nDefinition succ (n : cnat) : cnat :=\n  fun X s z => s (n X s z).\n```\n\n### `plus`\n\n```haskell\nplus = \\m n -> m scc n\nplus = \\m n s z -> m s (n s z)\n```\n\n```coq\nDefinition plus (n m : cnat) : cnat :=\n  n cnat succ m.                (* System F *)\n  fun X s z => n X s (m X s z). (* Coq *)\n```\n\n```f(TAPL)\nplus = \n  lambda m:CNat.\n  lambda n:CNat. ( \n    lambda X.\n    lambda s:X->X.\n    lambda z:X. \n      m [X] s (n [X] s z)\n  ) as CNat;\n\nplus = \n  lambda m:CNat.\n  lambda n:CNat. \n    m [CNat] succ' n;\n```\n\n### `mult`\n\n```haskell\nmult = \\m n -> m (plus n) n0 \n```\n\n```coq\nDefinition mult (n m : cnat) : cnat :=\n  n cnat (plus m) zero.         (* SystemF *)\n  fun X s z => (m X (n X s) z). (* Coq *)\n```\n\n```f(TAPL)\nmult = \n  lambda m:CNat.\n  lambda n:CNat. \n    m [CNat] (plus n) c0;   /* partial app `plus` */\n```\n\n\n### `exp`\n\n```haskell\npow = \\m n -> m (mult n) n1\nexp = \\m n -> n m\n```\n\n```coq\nDefinition exp (n m : cnat) : cnat :=\n  n cnat (mult m) one         (* SystemF *)\n  fun X => m (X -> X) (n X).  (* Coq *)\n```\n\n"
  },
  {
    "path": "_posts/read_sf_lf/2019-01-05-sf-lf-05-tactics.md",
    "content": "---\ntitle: \"「SF-LC」5 Tactics\"\nsubtitle: \"Logical Foundations - More Basic Tactics\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - LF (逻辑基础)\n  - SF (软件基础)\n  - Coq\n  - 笔记\n---\n\n## `apply`\n\n- _exactly_ the same as some hypothesis\n- can be used to __finish__ a proof (shorter than `rewrite` then `reflexivity`)\n\nIt also works with _conditional_ hypotheses:\n\n```coq\nn, m, o, p : nat\neq1 : n = m\neq2 : forall q r : nat, q = r -> [q; o] = [r; p]\n============================\n[n; o] = [m; p]\n\napply eq2.\nn = m\n```\n\nIt works by working backwards. \nIt will try to _pattern match_ the universally quantified `q r`. (i.e. universal var)\nWe match the _conclusion_ and generates the _hypothesis_ as a _subgoal_.\n\n```coq\nTheorem trans_eq : forall (X:Type) (n m o : X), n = m -> m = o -> n = o.\n\nExample trans_eq_example' : forall (a b c d e f : nat),\n     [a;b] = [c;d] -> [c;d] = [e;f] -> [a;b] = [e;f].\nProof. \n  intros a b c d e f eq1 eq2.\n  apply trans_eq. (* Error: Unable to find an instance for the variable m. *)\n```\n\nThe _unification algo_ won't happy since:\n- it can find instance for `n = o` from `[a;b] = [e;f]` (matching both conclusion)\n- but what should be `m`? It could be anything as long as `n = m` and `m = o` holds.\n\nSo we need to tell Coq explicitly which value should be picked for `m`:\n\n```coq\napply trans_eq with (m:=[c;d]).   (* <- supplying extra info, [m:=] can be ommited *)\n```\n\n> Prof Mtf: As a PL person, you should feel this is a little bit awkward since now function argument name must be remembered. (but it's just local and should be able to do any alpha-conversion).\n> named argument is more like a record.\n\nIn Coq Intensive 2 (2018), someone proposed the below which works:\n\n```coq\nExample trans_eq_example'' : forall (a b c d e f : nat),\n  [a;b] = [c;d] -> [c;d] = [e;f] -> [a;b] = [e;f]. \nProof.\n  intros a b c d e f.\n  apply trans_eq.          (* Coq was able to match three at all at this time...hmm *)\nQed.\n\n```\n\n\n## `injection` and `discrinimate`\n\n### Side Note on Terminologys of Function\n\n                         relation\n\n> function is defined as _a special kind of binary relation_. \n> it requires `xRy1 ∧ xRy2 → y1 = y2`  called \"functional\" or \"univalent\", \"right-unique\", or \"deterministic\"\n> and also `∀x ∈ X, ∃y ∈ Y s.t. xRy`   called \"left-total\"\n\n                    x       ↦      f(x)\n                  input     ↦     output\n                argument    ↦     value\n\n                    X       ↦       Y\n                 domain 域  ↦  co-domain 陪域      \n           what can go into ↦  what possibly come out\n\n                  A ⊆ X     ↦  f(A) = {f(x) | x ∈ A}\n                            ↦     image\n                            ↦  what actually come out\n\n    f⁻¹(B)={x ∈ X|f(x) ∈ B} ↦     B ⊆ Y\n                 preimage   ↦\n\n                when A = X  ↦       Y\n                            ↦     range  \n                               image of domain\n\nBesides subset, the notation of `image` and `pre-image` can be applied to _element_ as well.\nHowever, by definition:\n- the image    of an element `x` of domain    ↦  always single element of codomain (singleton set)\n- the preimage of an element `y` of codomain  ↦  may be empty, or one, or many!\n  - `<= 1 ↦ 1` : injective   (left-unique)\n  - `>= 1 ↦ 1` : surjective  (right-total)\n  - `   1 ↦ 1` : bijective\n\nNoted that the definition of \"function\" doesn't require \"right-total\"ity) until we have `surjective`.\n\ngraph = `[(x, f(x))]`, these points form a \"curve\", 真的是图像\n\n### Total vs Partial\n\nFor math, we seldon use partial function since we can simply \"define a perfect domain for that\".\nBut in Type Theory, Category Theory, we usually consider the _domain_ `X` and the _domain of definition_ `X'`.\n\nBesides, `f(x)` can be `undefined`. (not \"left-total\", might not have \"right\")\n\n### Conclusion - the road from Relation to Function\n\n\n                bi-relation \n                     | + right-unique \n              partial function\n                     | + left-total   \n              (total) function\n     + left-unique /   \\ + right-total\n          injection     surjection\n                   \\   /\n                 bijection\n\n\n\n### Original notes on [Injective, surjective, Bijective](https://en.wikipedia.org/wiki/Function)\n\nAll talk about the propeties of _preimage_!\n\n- Injective:  `<= 1 ↦ 1` or `0, 1 ↦ 1` (distinctness) \n- Surjective: `>= 1 ↦ 1` (at least 1 in the domain)\n- Bijective:  `   1 ↦ 1` (intersection of Inj and Surj, so only `1` preimage, _one-to-one correspondence_)\n\n\n### _injectivitiy_ and _disjointness_, or `inversion`.\n\nRecall the definition of `nat`:\n\n```coq\nInductive nat : Type :=\n| O : nat\n| S : nat → nat.\n```\n\nBesides there are two forms of `nat` (for `destruct` and `induction`), there are more facts:\n\n1. The constructor `S` is _injective_ (distinct), i.e `S n = S m -> n = m`.\n2. The constructors `O` and `S` are _disjoint_, i.e. `forall n, O != S n `.\n\n\n### `injection`\n\n- can be used to prove the _preimages_ are the same.\n- `injection` leave things in conclusion rather than hypo. with `as` would be in hypo.\n\n\n### `disjoint`\n\n- _principle of explosion_ (a logical principle)\n  - asserts a contraditory hypothesis entails anything. (even false things)\n  - _vacously true_\n- `false = true` is contraditory because they are distinct constructors.\n\n### `inversion`\n\n- the big hammer: inversion of the definition.\n- combining `injection` and `disjoint` and even some more `rewrite`.\n  - IMH, which one to use depends on _semantics_\n\nfrom Coq Intensive (not sure why it's not the case in book version).\n\n```coq\nTheorem S_injective_inv : forall (n m : nat),\n  S n = S m -> n = m.\nProof.\n  intros n m H. inversion H. reflexivity. Qed. \n\n\nTheorem inversion_ex1 : forall (n m : nat),\n  [n] = [m] -> n = m.\nProof.\n  intros n m H. inversion H. reflexivity. Qed.\n```\n\n> Side question: could Coq derive equality function for inductive type?\n> A: nope. Equality for some inductive types are _undecidable_.\n\n### Converse of injectivity\n\n```coq\nTheorem f_equal : ∀(A B : Type) (f: A → B) (x y: A),\n  x = y → f x = f y.\nProof. \n  intros A B f x y eq. \n  rewrite eq. reflexivity. Qed.\n```\n\n\n### Slide Q&A 1\n\n1. The tactic fails because tho `negb` is injective but `injection` only workks on constructors.\n\n## Using Tactics in Hypotheses\n\n### Reasoning Backwards and Reasoning Forward (from Coq Intensive 2)\n\nStyle of reasoning\n\n- Backwards: start with _goal_, applying tactics `simpl/destruct/induction`, generate _subgoals_, until proved.\n  - iteratively reasons about what would imply the goal, until premises or previously proven theorems are reached.\n- Forwards:  start with _hypo_, applying tactics, iteratively draws conclusions, until the goal is reached. \n\nBackwards reasoning is dominated stratgy of theroem prover (and execution of prolog). But not natural in informal proof.\n\n> True forward reasoning derives fact, but in Coq it's like hypo deriving hypo, very imperative.\n\n### `in`\n\n> most tactics also have a variant that performs a similar operation on a statement in the context.\n\n```coq\nsimpl in H.\nsimpl in *. (* in all hypo and goal *)\n\nsymmetry in H.\napply L in H.\n```\n\n### `apply`ing in hypothesis and in conclusion\n\n`apply`ing in hypo is very different with `apply`ing in conclusion.\n\n> it's not we unify the ultimate conclusion and generate premises as new goal, but trying to find a hypothesis to match and left the residual conclusion as new hypothesis.\n\n```coq\nTheorem silly3'' : forall (n : nat),\n  (true = (n =? 5) -> true = ((S (S n)) =? 7)) ->\n  true = (n =? 5)  ->\n  true = ((S (S n)) =? 7).\nProof.\n  intros n eq H.\n  apply eq in H.  (* or *)  apply eq. (* would be different *)\n  apply H.  Qed.\n```\n\nAlso if we add one more premises `true = true ->`, \nthe subgoal generated by `apply` would be in reversed order: \n\n```coq\nTheorem silly3'' : forall (n : nat),\n  (true = true -> true = (n =? 5) -> true = ((S (S n)) =? 7)) ->\n  true = (n =? 5)  ->\n  true = ((S (S n)) =? 7).\nProof.\n```\n> Again: \"proof engineering\": proof can be done in so many different ways and in different orders.\n\n\n## Varying the Induction Hypothesis\n\nSometimes it's important to control the exact form of the induction hypothesis!!\n\nConsidering:\n\n```coq\nTheorem double_injective: ∀n m,\n        double n = double m → n = m.\n```\n\nif we begin with `intros n m. induction n.`\nthen we get stuck in the inductive case of `n`, where the induction hypothesis `IHn'` generated is:\n\n```coq\nIHn' : double n' = double m -> n' = m\nIHn' : double n' = double (S m') -> n' = S m'  (* m = S m' *)\n```\n\nThis is not what we want!! \n\nTo prove `double_injective`, we hope `IHn'` can give us `double n' = double m' -> n' = m'` (i.e. the `P(n-1)` case).\n\nThe problem is `intros` implies _for these particular `n` and `m`_. (not more `forall` but _const_).  And when we `intros n m. induction n`, we are trying to prove a statement involving _every_ n but just a _single_ m...\n\n\n### _How to keep `m` generic (universal)?_\n\nBy either `induction n` before `intros m` or using `generalize dependent m`, we can have:\n\n```coq\nIHn' : forall m : nat, double n' = double m -> n' = m\n```\nwhere the `m` here is still universally quantified, so we can instaniate `m` with `m'` by `apply`ing it with `double n' = double m'` to yield `n' = m'` or vice versa. (recall conditional statements can be `apply`ed in 2 ways.)\n\n\n### Notes on `generalize dependent`\n\nUsually used when the argument order is conflict with instantiate (`intros`) order.\n\n> ? _reflection_: turing a computational result into a propositional result \n\n\n\n## Unfolding Definitions.\n\n> tactics like `simpl`, `reflexivity`, and `apply` will often unfold the definitions of functions automatically.\n> However, this automatic unfolding is somewhat _conservative_. \n\n`simpl.` only do unfolding when it can furthur simplify after unfolding. But sometimes you might want to explicitly `unfold` then do furthur works on that.\n\n\n## Using `destruct` on Compound Expressions\n\ndestruct the whole arbitrary expression.\n\n`destruct` by default throw away the whole expression after it, which might leave you into a stuck state.\nSo explicitly saying `eqn:Name` would help with that!\n\n\n## Micro Sermon - Mindless proof-hacking\n\nFrom Coq Intensive...\n\n- a lot of fun \n- ...w/o thinking at all\n- terrible temptation\n- you shouldn't always resist...\n\nBut after 5 mins...you should step back and try to think\n\nA typical coq user\n- sitting and does not have their brain engaged all the time...\n- at some point...(get stuck)\n  - oh I have to reengage brain..\n\nwhat is this really saying...\n\nOne way: good old paper and pencil\n\n5 mins is good time!\n\n\n"
  },
  {
    "path": "_posts/read_sf_lf/2019-01-06-sf-lf-06-logic.md",
    "content": "---\ntitle: \"「SF-LC」6 Logic\"\nsubtitle: \"Logical Foundations - Logic in Coq\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - LF (逻辑基础)\n  - SF (软件基础)\n  - Coq\n  - 笔记\n---\n\nWe have seen...\n\n* _propositions_: factual claims\n  - equality propositions (`e1 = e2`)\n  - implications (`P → Q`)\n  - quantified propositions (`∀ x, P`)\n* _proofs_: ways of presenting __evidence__ for the truth of a proposition\n\n\n`Prop` type\n-----------\n\n```coq\nCheck 3 = 3.  (* ===> Prop. A provable prop *)\nCheck 3 = 4.  (* ===> Prop. A unprovable prop *)\n```\n\n`Prop` is _first-class entity_ we can \n- name it\n- _parametrized_!\n\n```coq\nDefinition is_three (n : nat) : Prop :=\n  n = 3.\n\nCheck is_three. (* ===> nat -> Prop *)\n```\n\n### Properties\n\n> In Coq, _functions that return propositions_ are said to define _properties_ of their arguments.\n\n```coq\nDefinition injective {A B} (f : A → B) :=\n  ∀x y : A, f x = f y → x = y.\nLemma succ_inj : injective S. (* can be read off as \"injectivity is a property of S\" *)\nProof. \n  intros n m H. injection H as H1. apply H1. Qed.\n```\n\nThe equality operator `=` is also a function that returns a `Prop`. (property: _equality_)\n\n```coq\nCheck @eq. (* ===> forall A : Type, A -> A -> Prop *)\n```\n\nTheroems are types, and proofs are existentials.\n\n\nSlide Q&A - 1.\n--------------\n\n1. `Prop`\n2. `Prop`\n3. `Prop`\n4. Not typeable\n5. `nat -> nat`\n6. `nat -> Prop`\n7. (3)\n\n\nthink that Big Lambda (the type abstraction) works at type level, accepts type var, substitute and reture a type.\n`forall` in Coq is same (the concrete syntax) and only typecheck with `Type` or its subtype `Set` & `Prop`.\n\n```coq\nCheck (∀n:nat, S (pred n)).  (* not typeable *)\n\nDefinition foo : (forall n:nat, bool) (* foo: nat -> bool *)\n  := fun x => true.\n```\n\n\nLogical Connectives\n-------------------\n\n> noticed that connectives symbols are \"unicodize\" in book and spacemacs.\n\n\n### Conjuction (logical and)\n\n`and` is just binary `Prop -> Prop -> Prop` and associative. \n\n```coq\nPrint \"/\\\".\nInductive and (A B : Prop) : Prop :=  conj : A -> B -> A /\\ B\nCheck and. (* ===> and : Prop -> Prop -> Prop *)\n```\n\n#### and introduction\n\n```coq\nLemma and_intro : forall A B : Prop, A -> B -> A /\\ B.\nProof.\n  intros A B HA HB. split.\n  - apply HA.\n  - apply HB.\nQed.\n```\n> To prove a conjunction, \n> - use the `split` tactic. It will generate two subgoals,\n> - or use `apply and_intro.`, which match the conclusion and give its two premises as your subgoals.\n\n\n#### and elimination\n\nif we already have a proof of `and`, `destruct` can give us both.\n\n```coq\nLemma and_example2' :\n  ∀n m : nat, n = 0 ∧ m = 0 → n + m = 0.\nProof.\n  intros n m [Hn Hm]. (* = intro H. destruct H. *)\n  rewrite Hn. rewrite Hm. reflexivity. Qed.  (* you could use only one *)\n```\n\nInstead of packing into conjunction `∀n m : nat, n = 0 ∧ m = 0 → n + m = 0.`\nwhy not two separate premises?      `∀n m : nat, n = 0 -> m = 0 → n + m = 0.` \nBoth are fine in this case but conjunction are useful as intermediate step etc.\n\n> Coq Intensive Q: why `destruct` can work on `and`? is `and` inductively defined?\n> A: Yes.\n\n\n\n### Disjunction (locial or)\n\n#### or elimination\n\nWe need do case analysis (either `P` or `Q` should be able to prove the theroem separately!)\n\n```coq\nLemma or_example :\n  forall n m : nat, n = 0 \\/ m = 0 -> n * m = 0.\nProof.\n  (* This pattern implicitly does case analysis on [n = 0 \\/ m = 0] *)\n  intros n m [Hn | Hm]. (* = intro H. destruct H. *)\n  - (* Here, [n = 0] *) rewrite Hn. reflexivity.\n  - (* Here, [m = 0] *) rewrite Hm. rewrite <- mult_n_O. reflexivity.\nQed.\n```\n\n#### or introduction\n\nWhen trying to establish (intro into conclusion) an `or`, using `left` or `right` to pick one side to prove is sufficient.\n\n```coq\nLemma or_intro : forall A B : Prop, A -> A \\/ B.\nProof.\n  intros A B HA.\n  left.  (* tactics *)\n  apply HA.\nQed.\n```\n\n\n\n### Falsehood and negation\n\n#### False?\n\nRecall the _princple of explosion_: it asserts that, if we assume a _contradiction_, then any other proposition can be derived.\nwe could define `¬ P` (\"not P\") as `∀ Q, P → Q.`. \n\n> Coq actually makes a slightly different (but equivalent) choice, defining `¬ P as P → False`, where `False` is a specific *contradictory proposition* defined in the standard library. \n\n```coq\nDefinition not (P:Prop) := P → False.\nNotation \"¬x\" := (not x) : type_scope.\n```\n\nProve the _princple of explosion_:\n\n```coq\nTheorem ex_falso_quodlibet : forall (P:Prop),\n  False -> P.\nProof.\n  intros P contra.\n  destruct contra.  Qed.  (* 0 cases to prove since ⊥ is not provable. [inversion] also works *)\n```\n\n\n#### Inequality\n\n```coq\nNotation \"x <> y\" := (~(x = y)).\n```\n\nSame as SML and OCaml (for structural equality, OCaml uses `!=` for physical equality.)\n\n\n#### Proving of negation (or how to prove `¬P`)\n\nthinking about as `unfold not`, i.e. `P -> False`.\nso you have an assumptions `P` that could be `intros HP.` and the residual goal would be simply `False`. \nwhich is usually proved by some kind of contradiction in hypotheses with tactics `discriminate.` or `contradiction.`\n\n```coq\nTheorem contradiction_implies_anything : forall P Q : Prop,\n  (P /\\ ~P) -> Q.\nProof.\n  intros P Q [HP HNA].                 (* we could [contradiction.] to end the proof here`*)\n  unfold not in HNA. apply HNA in HP.  (* HP : False, HNA : P -> False  ⊢  HP: False  *)\n  destruct HP.  Qed.                   (* destruct False.  *)\n```\n\n#### Tactic `exfalso.` \n\n> If you are trying to prove a goal that is nonsensical (e.g., the goal state is `false = true`), apply `ex_falso_quodlibet` to change the goal to `False`. This makes it easier to use assumptions of the form `¬P` that may be available in the context — in particular, assumptions of the form `x≠y`.\n\n> Since reasoning with `ex_falso_quodlibet` is quite common, Coq provides a built-in tactic, `exfalso`, for applying it.\n\n\n\n## Slide Q&A - 2\n\n> ?`unfold` is implicit\n\n1. only `destruct` (if we consider `intros` destruct is also `destruct`.), ?`unfold`\n2. none (?`unfold`)\n3. `left.`\n4. `destruct`, `unfold`, `left` and `right`\n5. `discrinminate` (or `inversion`)\n\n\n\n\n### Truth\n\n```coq\nLemma True_is_true : True.\nProof. apply I. Qed.\n```\n\n`I : True` is a predefined Prop...\n\n\n\n### Logical Equivalence\n\n_if and only if_ is just the conjunction of two implications. (so we need `split` to get 2 subgoals)\n\n```coq\nDefinition iff (P Q : Prop) := (P → Q) ∧ (Q → P).\nNotation \"P ↔ Q\" := (iff P Q)\n                    (at level 95, no associativity) : type_scope.\n```\n\n> `rewrite` and `reflexivity` can be used with iff statements, not just equalities.\n\n\n\n\n### Existential Quantification\n\nTo prove a statement of the form `∃x, P`, we must show that `P` holds for some specific choice of value for `x`, \nknown as the __witness__ of the existential.\n\nSo we explicitly tell Coq which witness `t` we have in mind by invoking `exists t`.\nthen all occurences of that \"type variable\" would be replaced.\n\n#### Intro\n\n```coq\nLemma four_is_even : exists n : nat, 4 = n + n.\nProof.\n  exists 2. reflexivity.\nQed.\n```\n\n#### Elim\n\nBelow is an interesting question...by intros and destruct we can have equation `n = 4 + m` in hypotheses.\n\n```coq\nTheorem exists_example_2 : forall n,\n  (exists m, n = 4 + m) ->\n  (exists o, n = 2 + o).\nProof.\n  intros n [m Hm]. (* note implicit [destruct] here *)\n  exists (2 + m).\n  apply Hm.  Qed.\n```\n\n\n\n## Programming with Propositions\n\nConsidering writing a common recursive `is_in` for polymorphic lists. \n(Though we dont have a polymorphic `=?` (`eqb`) defined yet) \n\n```coq\nFixpoint is_in {A : Type} (x : A) (l : list A) : bool :=\n  match l with\n  | [] => false\n  | x' :: l' => if (x' =? x) then true else is_in x l'\n  end.\n```\n\nSimilarly, we can write this function but with disjunction and return a `Prop`!\n_so we can write function to generate/create statements/propositions!_ (thx for the idea Prop is first-class)\n\n```coq\nFixpoint In {A : Type} (x : A) (l : list A) : Prop :=\n  match l with\n  | [] => False\n  | x' :: l' => x' = x ∨ In x l'\n  end.\n```\n\nThe whole thing I understood as a _type operator_ (function in type level) and it's _recursive_!\n\nCoq dare to do that becuz its _total_, which is guarded by its _termination checker_.\nun-total PL, if having this, would make its type system _Turing Complete_ (thus having _Halting Problem_).\n(Recursive Type like ADT/GADT in ML/Haskell is a limited form of recursion allowing no arbitray recursion.)\n\n\n\n### In_map\n\nI took this one since it's like a formal version of _Property-based Tests_!.\n\n```coq\nLemma In_map :\n  forall (A B : Type) (f : A -> B) (l : list A) (x : A),\n    In x l ->\n    In (f x) (map f l).\nProof.\n  intros A B f l x.\n  induction l as [|x' l' IHl'].\n  - (* l = nil, contradiction *)\n    simpl. intros [].\n  - (* l = x' :: l' *)\n    simpl. intros [H | H].           (* evaluating [In] gives us 2 cases:  *)\n    + rewrite H. left. reflexivity.  (* in head of l *)\n    + right. apply IHl'. apply H.    (* in tail of l*)\nQed.\n```\n\n> Q & A: \n> 1. `eq` is just another inductively defined and doesn't have any computational content. (satisfication)\n> 2. Why use `Prop` instead of `bool`? See _reflection_ below.\n\n\n### Drawbacks\n\n> In particular, it is subject to Coq's usual restrictions regarding the definition of recursive functions, \n> e.g., the requirement that they be \"obviously terminating.\"\n\n> In the next chapter, we will see how to define propositions _inductively_, \n> a different technique with its own set of strengths and limitations.\n\n\n\n## Applying Theorems to Arguments.\n\n### `Check some_theorem` print the statement!\n\n```coq\nCheck plus_comm.\n(* ===> forall n m : nat, n + m = m + n *)\n```\n\n> Coq prints the _statement_ of the `plus_comm` theorem in the same way that it prints the _type_ of any term that we ask it to Check. Why?\n\nHmm...I just noticed that!!\nBut I should notice because __Propositions are Types! (and terms are proof)__\n\n\n### Proof Object\n\n> _proofs_ as first-class objects.\n\nAfter `Qed.`, Coq defined they as _Proof Object_ and the _type of this obj_ is the statement of the theorem.\n\n> Provable: some type is inhabited by some thing (having terms).\n\nSo I guess when we apply theorems, Coq implicitly use the type of the Proof Object. (it's already type abstraction)\n...we will get to there later at ProofObject chapter.\n\n\n### Apply theorem as function\n\n> `rewrite` select variables greedily by default\n\n```coq\nLemma plus_comm3_take3 :\n  ∀x y z, x + (y + z) = (z + y) + x.\nProof.\n  intros x y z.\n  rewrite plus_comm.\n  rewrite (plus_comm y z).     (* we can explicitly provide type var! *)\n  reflexivity.\nQed.\n```\n\n`x y z` were some type var and _instantiated to values_ by `intros`, e.g. `x, y, z:nat` \nbut we can explicilty pass in to `plus_comm`, which is a forall type abstraction! (`Δ n m. (eq (n + m) (m + n))`) \n\n> there must be something there in Proof Object so we can apply _values_ to a _type-level function_\n\n\n\n\n## Coq vs. Set Theory\n\nCoq's logical core, _the Calculus of Inductive Constructions_, is a _metalanguage for math_, but differs from other foundations of math e.g. ZFC Set Theory\n\n### Functional Extensionality\n\n```coq\n(∀x, f x = g x) → f = g\n\n∃f g, (∀x, f x = g x) → f = g\n\n∃f g, (∀x, f x = g x) ∧ f != g    (* negation, consistent but not interesting... *)\n```\n\n> In common math practice, two functions `f` and `g` are considered equal if they produce the same outputs.\n> This is known as the principle of _functional extensionality_.\n\n> Informally speaking, an \"extensional property\" is one that pertains to an object's observable behavior. \n> <https://en.wikipedia.org/wiki/Extensionality>\n> <https://en.wikipedia.org/wiki/Extensional_and_intensional_definitions>?\n\nThis is not built-in Coq, but we can add them as Axioms. \nWhy not add everything?\n> 1. One or multiple axioms combined might render _inconsistency_.\n> 2. Code extraction might be problematic\n\n> Fortunately, it is known that adding functional extensionality, in particular, is consistent.\n> [consistency](https://en.wikipedia.org/wiki/Consistency):\n   a consistent theory is one that does not contain a contradiction.\n\n### Adding Axioms\n\n```coq\nAxiom functional_extensionality : forall {X Y: Type}\n                                    {f g : X -> Y},\n  (forall (x:X), f x = g x) -> f = g.\n```\n\nIt's like `Admitted.` but alerts we're not going to fill in later. \n\n\n### Exercise - Proving Reverse with `app` and with `cons` are fn-exensionally equivalent.\n\n```coq\nFixpoint rev_append {X} (l1 l2 : list X) : list X :=\n  match l1 with\n  | [] => l2\n  | x :: l1' => rev_append l1' (x :: l2)\n  end.\n\nDefinition tr_rev {X} (l : list X) : list X :=\n  rev_append l [].\n```\n\nBTW, this version is `tail recursive` becuz the recursive call is the last operation needs to performed.\n(In `rev` i.e. `rev t ++ [h]`, recursive call is a argument of function `++` and we are CBV.)\n\n```coq\nLemma tr_rev_correct : forall X, @tr_rev X = @rev X.\n```\n\n\n\n### Propositions and Booleans\n\n> We've seen two different ways of expressing logical claims in Coq: \n> 1. with booleans     (of type `bool`),  ; computational way\n> 2. with propositions (of type `Prop`).  ; logical way\n\nThere're two ways to define 42 is even:\n\n```coq\nExample even_42_bool : evenb 42 = true.\nExample even_42_prop : ∃k, 42 = double k.\n```\n\nWe wanna show there are _interchangable_.\n\n```coq\nTheorem even_bool_prop : ∀n,\n  evenb n = true ↔ ∃k, n = double k.\n```\n\n> In view of this theorem, we say that the \n> boolean computation `evenb n` _reflects_ the truth of the proposition `∃ k, n = double k`.\n\nWe can futhur general this to any equations representing as `bool` or `Prop`:\n\n```coq\nTheorem eqb_eq : ∀n1 n2 : nat,\n  n1 =? n2 = true ↔ n1 = n2.\n```\n\n#### Notes on Computability.\n\n> However, even they are equivalent from a purely logical perspective, \n> they may not be equivalent `operationally`.\n\n```coq\nFail Definition is_even_prime n :=\n  if n = 2 then true\n  else false.\n\nError: The term \"n = 2\" has type \"Prop\" which is not a (co-)inductive type.\n```\n\n`=`, or `eq`, as any function in Coq, need to be computable and total. And we have no way to tell _whether any given proposition is true or false_. (...We can only naturally deduce things are inductively defined)\n\n> As a consequence, Prop in Coq does not have a universal case analysis operation telling whether any given proposition is true or false, since such an operation would allow us to write non-computable functions.\n\n> Although general non-computable properties cannot be phrased as boolean computations, it is worth noting that even many computable properties are easier to express using Prop than bool, since recursive function definitions are subject to significant restrictions in Coq.\n\nE.g. Verifying Regular Expr in next chapter. \n> Doing the same with `bool` would amount to writing a _full regular expression matcher_ (so we can execute the regex).\n\n\n#### Proof by Reflection!\n\n```coq\n(* Logically *)\nExample even_1000 : ∃k, 1000 = double k.\nProof. ∃500. reflexivity. Qed.\n\n(* Computationally *)\nExample even_1000' : evenb 1000 = true.\nProof. reflexivity. Qed.\n\n(* Prove logical version by reflecting in computational version *)\nExample even_1000'' : ∃k, 1000 = double k.\nProof. apply even_bool_prop. reflexivity. Qed.\n```\n\n> As an extreme example, the Coq proof of the famous _4-color theorem_ uses reflection to reduce the analysis of hundreds of different cases to a boolean computation.\n\n\n\n### Classical vs. Constructive Logic\n\n\n...\n\n\n\n## Future Schedule\n\n> Proof got messier!\n> Lean on your past PLT experience\n\n\nAs discussion leader\n\n- having many materials now\n- selected troublesome and interesting ones\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "_posts/read_sf_lf/2019-01-07-sf-lf-07-indprop.md",
    "content": "---\ntitle: \"「SF-LC」7 Ind Prop\"\nsubtitle: \"Logical Foundations - Inductively Defined Propositions (归纳定义命题)\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - LF (逻辑基础)\n  - SF (软件基础)\n  - Coq\n  - 笔记\n---\n\nInductively Defined Propositions \n--------------------------------\n\n### The 3rd way to state Evenness...\n\nBesides: \n\n```coq\nTheorem even_bool_prop : ∀n,\n  evenb n = true ↔ ∃k, n = double k.\n (*bool*)                 (*prop*)\n```\n\nwe can write an _Inductive definition_ of the `even` property!\n\n\n### Inference rules\n\nIn CS, we often uses _inference rules_ \n\n                        ev n\n    ---- ev_0       ------------ ev_SS\n    ev 0            ev (S (S n))\n\nand _proof tree_ (i.e. evidence), there could be multiple premieses to make it more tree-ish.\n\n    ---- ev_0\n    ev 0\n    ---- ev_SS\n    ev 2\n    ---- ev_SS\n    ev 4\n\nSo we can literally translate them into a GADT:\n\n\n### Inductive Definition of Evenness\n\n```coq\nInductive even : nat → Prop :=\n  | ev_0  : even 0\n  | ev_SS : ∀n, even n → even (S (S n)). \n\nCheck even_SS.\n(* ==> : forall n : nat, even n -> even (S (S n)) *)\n```\n\nThere are two ways to understand the `even` here:\n\n\n### 1. A Property of `nat` and two theorems (Intuitively) \n\n> the thing we are defining is not a `Type`, but rather a function `nat -> Prop` — i.e., a property of numbers. \n\nwe have two ways to provide an evidence to show the `nat` is `even`, either or:\n1. it's `0`, we can immediately conclude it's `even`.\n2. for any `n`, if we can provide a evidence that `n` is `even`, then `S (S n)` is `even` as well.\n\n> We can think of the definition of `even` as defining a Coq property `even : nat → Prop`, together with primitive theorems `ev_0 : even 0` and `ev_SS : ∀ n, even n → even (S (S n))`.\n\n\n### 2. An \"Indexed\" GADT and two constructors (Technically)\n\n> In an Inductive definition, an argument to the type constructor on the left of the colon is called a \"parameter\", whereas an argument on the right is called an \"index\". -- \"Software Foundaton\"\n\nConsidered a \"parametrized\" ADT such as the polymorphic list, \n\n```coq\nInductive list (X:Type) : Type :=\n  | nil\n  | cons (x : X) (l : list X).\n\nCheck list. (* ===> list : Type -> Type *)\n```\n\nwhere we defined type con `list : Type -> Type`, by having a type var `X` in the left of the `:`.\nthe `X` is called a _parameter_ and would be _parametrized i.e. substituted, globally_, in constructors.\n\n\n\nHere, we write `nat` in the right of the `:` w/o giving it a name (to refer and to substitute),\nwhich allows the `nat` taking different values in different constructors (as constraints).\nit's called an _index_ and will form a family of type indexed by `nat` (to type check?)\n\n\nFrom this perspective, there is an alternative way to write this GADT:\n\n```coq\nInductive even : nat → Prop :=\n| ev_0                         : even 0\n| ev_SS (n : nat) (H : even n) : even (S (S n)).\n```\n\nwe have two ways to construct the `even` type (`Prop <: Type`), either or:\n1. `ev_0` takes no argument, so simply instantiate `even` with `nat` 0\n2. `ev_SS` takes a `nat` `n` and a `H` typed `even n`, \n  - the _dependency_ between two arguments thus established! \n  - as long as the _constraint on same `n`_ is fullfilled, we can build type `even` with `S (S n)`\n  \nThe take way is that _dependent type (Pi-type)_ allow us to constriant constructors with different values.\n\n> _indexed_ way is more general. it formed a larger type, and is only used when extra power needed. \n> every parametrized one can be represented as indexed one (it's just that index happended to be the same)\n\n\n### \"Constructor Theorems\"\n\n> Such \"constructor theorems\" have the same status as proven theorems. In particular, we can use Coq's `apply` tactic with the rule names to prove `even` for particular numbers...\n\n```coq\nTheorem ev_4 : even 4.\nProof. apply ev_SS. apply ev_SS. apply ev_0. Qed.\n```\n\nProof States Transition:\n\n    even 4\n    ------ apply ev_SS.\n    even 2\n    ------ apply ev_SS.\n    even 0\n    ------ apply ev_0.\n           Qed.\n\n\nI believed what `apply` do is trying to _backward reasoning_, i.e. matching the goal and leave the \"evidence\" need to be proved (to conclude the goal).\n\nwe can write it as normal function application syntax w/o using tactics like other Dependent-typed PL as well\n\n```coq\nTheorem ev_4' : even 4.\nProof. apply (ev_SS 2 (ev_SS 0 ev_0)). Qed.\n```\n\n\nUsing Evidence in Proofs\n------------------------\n\n> Besides _constructing evidence_ that numbers are even, we can also _reason_ about such evidence.\n\n> Introducing `even` with an `Inductive` declaration tells Coq that these two constructors are the __only__ ways to build evidence that numbers are `even`. \n\n> In other words, if someone gives us evidence `E` for the assertion `even n`, then we know that `E` must have one of two shapes\n\n> This suggests that it should be possible to analyze a hypothesis of the form `even n` much _as we do inductively defined data structures_; in particular, it should be possible to argue by __induction__ and __case analysis__ on such evidence.\n\nThis starts to get familiar as what we did for many calculi, ranging from Logics to PLT.\nThis is called the __Inversion property__.\n\n\n### Inversion on Evidence\n\nWe can prove the inersion property by ourselves:\n\n```coq\nTheorem ev_inversion :\n  ∀(n : nat), even n →\n    (n = 0) ∨ (∃n', n = S (S n') ∧ even n').\nProof.\n  intros n E.\n  destruct E as [ | n' E'].\n  - (* E = ev_0 : even 0 *)                  left. reflexivity.\n  - (* E = ev_SS n', E' : even (S (S n')) *) right. ∃n'. split. reflexivity. apply E'.\nQed.\n```\n\nBut Coq provide the `inversion` tactics that does more! (not always good tho, too automagical)\n\n> The inversion tactic does quite a bit of work. When applied to equalities, as a special case, it does the work of both `discriminate` and `injection`. In addition, it carries out the `intros` and `rewrite`s\n\n> Here's how inversion works in general. Suppose the name `H` refers to an assumption `P` in the current context, _where `P` has been defined by an `Inductive` declaration_. Then, for each of the constructors of `P`, `inversion H` generates a subgoal in which `H` has been replaced by the _exact, specific conditions under which this constructor could have been used to prove `P`_. \n> Some of these subgoals will be self-contradictory; inversion throws these away. The ones that are left represent the cases that must be proved to establish the original goal. For those, inversion adds all equations into the proof context that must hold of the arguments given to `P` (e.g., `S (S n') = n` in the proof of `evSS_ev`).\n(`9-proof-object.md` has a better explaination on `inversion`)\n\n`inversion` is a specific use upon `destruct` (both do case analysis on constructors), but many property need `induction`!. \nBy `induction (even n)`, we have cases and subgoals splitted, and induction hypothesis as well.\n\n\n### Induction on Evidence\n\nSimilar to induction on inductively defined data such as `list`: \n> To prove a property of (for any `X`)                       `list X` holds, we can use `induction` on `list X`.\n> To prove a property of `n` holds for all numbers for which `even n` holds, we can use `induction` on `even n`.\n\n\n#### Notes on induction\n\n_The principle of induction_ is to prove `P(n-1) -> P(n)` (多米诺) for some (well-founded partial order) set of `n`. \n\nHere, we are induction over \"the set of numbers fullfilling the property `even`\". \nNoticed that we r proving things over this set, meaning we already have it (i.e. a proof, or a evidence) in premises, instead of proving the `even`ness of the set.\n\n\n#### Proof by Mathematical Induction is Deductive Reasoning\n\n> \"Proof by induction,\" despite the name, is deductive. The reason is that proof by induction does not simply involve \"going from many specific cases to the general case.\" Instead, in order for proof by induction to work, we need a deductive proof that each specific case implies the next specific case. Mathematical induction is not philosophical induction. \n<https://math.stackexchange.com/a/1960895/528269>\n\n> Mathematical induction is an inference rule used in formal proofs. Proofs by mathematical induction are, in fact, examples of deductive reasoning.\n> Equivalence with the well-ordering principle: The principle of mathematical induction is usually stated as an axiom of the natural numbers; see Peano axioms. However, it can be proved from the well-ordering principle. Indeed, suppose the following:\n<https://en.wikipedia.org/wiki/Mathematical_induction>\n\n\n#### Also, Structual Induction is one kind of Math. Induction\n\n> 和标准的数学归纳法等价于良序原理一样，结构归纳法也等价于良序原理。\n\n> ...A _well-founded_ _partial order_ is defined on the structures...\n> ...Formally speaking, this then satisfies the premises of an _axiom of well-founded induction_...\n<https://en.wikipedia.org/wiki/Structural_induction>\n\nIn terms of Well-ordering and Well-founded:\n\n> If the set of all structures of a certain kind admits a well-founded partial order, \n> then every nonempty subset must have a minimal element. (This is the definition of \"well-founded\".)\n> 如果某种整个结构的集容纳一个良基偏序， 那么每个非空子集一定都含有最小元素。（其实这也是良基的定义\n\n\n\n\n\nInductive Relations\n-------------------\n\nJust as a single-argument proposition defines a _property_, 性质\na two-argument proposition defines a _relation_. 关系\n\n```coq\nInductive le : nat → nat → Prop :=\n  | le_n n                : le n n\n  | le_S n m (H : le n m) : le n (S m).\n\nNotation \"n ≤ m\" := (le n m).\n```\n\n> It says that there are two ways to _give evidence_ that one number is less than or equal to another:\n\n1. either same number\n2. or give evidence that `n ≤ m` then we can have `n ≤ m + 1`.\n\nand we can use the same tactics as we did for properties.\n\n\n\n\n## Slide Q&A - 1\n\n1. First `destruct` `even n` into 2 cases, then `discriminate` on each.\n\nAnother way... \nrewriting `n=1` on `even n`. It won't compute `Prop`, but `destruct` can do some `discriminate` behind the scene.\n\n\n\n## Slide Q&A - 2\n\n`inversion` and `rewrite plus_comm` (for `n+2`)\n\n\n\n\n`destruct` vs. `inversion` vs. `induction`.\n-------------------------------------------\n\n> `destruct`, `inversion`, `induction` (on general thing)... similar/specialized version of each...\n\nTrying to internalize this concept better: _When to use which?_\n\nFor any inductively defined proposition (`<: Type`) in hypothesis:\nmeaning from type perspective, it's already a \"proper type\" (`::*`)\n\n```coq\nInductive P = C1 : P1 | C2 : A2 -> P2 | ...\n```\n\n1. `destruct`     case analysis on inductive type \n\n* simply give you each cases, i.e. each constructors.\n* we can destruct on `a =? b` since `=?` is inductively defined.\n\n\n2. `induction`    use induction principle\n\n* proving `P` holds for all base cases\n* proving `P(n)` holds w/ `P(n-1)` for all inductive cases\n(`destruct` stucks in this case because of no induction hypothesis gained from induction principle)\n\n\n3. `inversion`    invert the conclusion and give you all cases with premises of that case.\n\nFor GADT, i.e. \"indexed\" `Prop` (property/relation), `P` could have many shape\n`inversion` give you `Ax` for shape `P` assuming built with `Cx`\n\n`inversion` discards cases when shape `P != Px`.\n(`destruct` stucks in this case because of no equation gained from inversion lemma)\n\n\n\n\n\n\nCase Study: Regular Expressions\n-------------------------------\n\n\n### Definition\n\n_Definition of RegExp in formal language can be found in FCT/CC materials_\n\n```coq\nInductive reg_exp {T : Type} : Type :=\n  | EmptySet                 (* ∅ *)\n  | EmptyStr                 (* ε *)\n  | Char (t : T)\n  | App (r1 r2 : reg_exp)    (* r1r2 *)\n  | Union (r1 r2 : reg_exp)  (* r1 | r2 *)\n  | Star (r : reg_exp).      (* r*  *)\n```\n\n\n> Note that this definition is _polymorphic_. \n> We depart slightly in that _we do not require the type `T` to be finite_. (difference not significant here)\n\n> `reg_exp T` describe _strings_ with characters drawn from `T` — that is, __lists of elements of `T`__. \n\n\n### Matching\n\nThe matching is somewhat similar to _Parser Combinator_ in Haskell... \n\ne.g.\n`EmptyStr` matches `[]`\n`Char x`   matches `[x]`\n\n> we definied it into an `Inductive` relation (can be displayed as _inference-rule_). \nsomewhat type-level computing !\n\n```coq\nInductive exp_match {T} : list T → reg_exp → Prop :=\n| MEmpty : exp_match [] EmptyStr\n| MChar x : exp_match [x] (Char x)\n| MApp s1 re1 s2 re2\n            (H1 : exp_match s1 re1)\n            (H2 : exp_match s2 re2) :\n            exp_match (s1 ++ s2) (App re1 re2)\n(** etc. **)\n\nNotation \"s =~ re\" := (exp_match s re) (at level 80).  (* the Perl notation! *)\n```\n\n## Slide Q&A - 3\n\nThe lack of rule for `EmptySet` (\"negative rule\") give us what we want as PLT\n\n\n### `Union` and `Star`.\n\n> the informal rules for `Union` and `Star` correspond to _two constructors_ each.\n\n```coq\n| MUnionL s1 re1 re2\n              (H1 : exp_match s1 re1) :\n              exp_match s1 (Union re1 re2)\n| MUnionR re1 s2 re2\n              (H2 : exp_match s2 re2) :\n              exp_match s2 (Union re1 re2)\n| MStar0 re : exp_match [] (Star re)\n| MStarApp s1 s2 re\n              (H1 : exp_match s1 re)\n              (H2 : exp_match s2 (Star re)) :\n              exp_match (s1 ++ s2) (Star re).\n```\n\nThinking about their _NFA_: they both have non-deterministic branches!\nThe recursive occurrences of `exp_match` gives as _direct argument_ (evidence) about which branches we goes.\n\n> we need some _sanity check_ since Coq simply trust what we declared...\n> that's why there is even Quick Check for Coq.\n\n### Direct Proof \n\nIn fact, `MApp` is also non-deterministic about how does `re1` and `re2` collaborate...\nSo we have to be explicit:\n\n```coq\nExample reg_exp_ex2 : [1; 2] =~ App (Char 1) (Char 2).\nProof.\n  apply (MApp [1] _ [2]).\n  ...\n```\n\n### Inversion on Evidence\n\nThis, if we want to prove via `destruct`, \nwe have to write our own _inversion lemma_ (like `ev_inversion` for `even`).\nOtherwise we have no equation (which we should have) to say `contradiction`.\n\n```coq\nExample reg_exp_ex3 : ~ ([1; 2] =~ Char 1).\nProof.\n  intros H. inversion H.\nQed.\n```\n\n### Manual Manipulation\n\n```coq\nLemma MStar1 :\n  forall T s (re : @reg_exp T) ,\n    s =~ re ->\n    s =~ Star re.\nProof.\n  intros T s re H.\n  rewrite <- (app_nil_r _ s).  (* extra \"massaging\" to convert [s] => [s ++ []] *)\n  apply (MStarApp s [] re).    (* to the shape [MStarApp] expected thus can pattern match on *)\n\n      (* proving [MStarApp] requires [s1 s2 re H1 H2]. By giving [s [] re], we left two evidence *)\n      | MStarApp s1 s2 re\n          (H1 : exp_match s1 re)\n          (H2 : exp_match s2 (Star re)) :\n          exp_match (s1 ++ s2) (Star re).\n\n  - apply H.                   (* evidence H1 *)\n  - apply MStar0.              (* evidence H2 *)\nQed.                           (* the fun fact is that we can really think the _proof_\n                                  as providing evidence by _partial application_. *)\n```\n\n### Induction on Evidence\n\n> By the recursive nature of `exp_match`, proofs will often require induction.\n\n```coq\n(** Recursively collecting all characters that occur in a regex **)\nFixpoint re_chars {T} (re : reg_exp) : list T :=\n  match re with\n  | EmptySet ⇒ []\n  | EmptyStr ⇒ []\n  | Char x ⇒ [x]\n  | App re1 re2 ⇒ re_chars re1 ++ re_chars re2\n  | Union re1 re2 ⇒ re_chars re1 ++ re_chars re2\n  | Star re ⇒ re_chars re\n  end.\n```\n\nThe proof of `in_re_match` went through by `inversion` on relation `s =~ re`. (which gives us all 7 cases.)\nThe interesting case is `MStarApp`, where the proof tree has two _branches_ (of premises):\n\n                      s1 =~ re    s2 =~ Star re\n                     ---------------------------            (MStarApp)\n                        s1 ++ s2 =~ Star re\n\nSo by induction on the relation (rule), we got _two induction hypotheses_!\nThat's what we need for the proof.\n\n\n\nThe `remember` tactic (Induction on Evidence of A Specific Case)\n----------------------------------------------------------------\n\nOne interesting/confusing features is that `induction` over a term that's _insuffciently general_. e.g. \n\n```coq\nLemma star_app: ∀T (s1 s2 : list T) (re : @reg_exp T),\n  s1 =~ Star re →\n  s2 =~ Star re →\n  s1 ++ s2 =~ Star re.\nProof.\n  intros T s1 s2 re H1.\n```\n\nHere, we know the fact that both `s1` and `s2` are matching with the form `Star re`. \nBut by `induction`. it will give us _all 7 cases_ to prove, but _5 of them are contradictory_!\n\nThat's where we need `remember (Star re) as re'` to get this bit of information back to `discriminate`.\n\n\n### Sidenotes: `inversion` vs. `induction` on evidence\n\nWe might attemp to use `inversion`,\nwhich is best suitted for have a specific conclusion of some rule and inverts back to get its premises.\n\nBut for _recursive cases_ (e.g. `Star`), we always need `induction`. \n\n`induction` on a specific conclusion then `remember + contradiction` is similar with how `inversion` solves contradictionary cases. (They both `destruct` the inductively defined things for sure)\n\n\n\n\nExercise: 5 stars, advanced (pumping)\n-------------------------------------\n\nFCT/Wikipedia \"proves\" [pumping lemma for regex](https://en.wikipedia.org/wiki/Pumping_lemma_for_regular_languages) in a non-constructive way.\n\nHere we attempts to give a constructive proof.\n\n\n\n\nCase Study: Improving Reflection (互映)\n-------------------------------------\n\n> we often need to relate boolean computations to statements in `Prop`\n\n```coq\nInductive reflect (P : Prop) : bool → Prop :=\n| ReflectT (H : P) : reflect P true\n| ReflectF (H : ¬P) : reflect P false.\n```\n\nThe _only_ way to construct `ReflectT/F` is by showing (a proof) of `P/¬P`,\nmeaning invertion on `reflect P bool` can give us back the evidence. \n\n\n`iff_reflect` give us `eqbP`.\n\n```coq\nLemma eqbP : ∀n m, reflect (n = m) (n =? m).\nProof.\n  intros n m. apply iff_reflect. rewrite eqb_eq. reflexivity.\nQed.\n```\n\nThis gives us a small gain in convenience: we immediately give the `Prop` from `bool`, no need to `rewrite`.\n> Proof Engineering Hacks...\n\n\n### SSReflect - small-scale reflection\n\n> a Coq library\n> used to prove 4-color theorem...!\n> simplify small proof steps with boolean computations. (somewhat automation with decision procedures)\n\n\n\n\n\n\nExtended Exercise: A Verified Regular-Expression Matcher\n--------------------------------------------------------\n\n> we have defined a _match relation_ that can _prove_ a regex matches a string.\n> but it does not give us a _program_ that can _run_ to determine a match automatically...\n\n> we hope to translate _inductive rules (for constructing evidence)_ to _recursive fn_.\n> however, since `reg_exp` is recursive, Coq won't accept it always terminates \n\ntheoritically, the regex = DFA so it is decidable and halt.\ntechnically, it only halts on finite strings but not infinite strings. \n(and infinite strings are probably beyond the scope of halting problem?)\n\n> Heavily-optimized regex matcher = translating into _state machine_ e.g. NFA/DFA.\n> Here we took a _derivative_ approach which operates purely on string.\n\n```coq\nRequire Export Coq.Strings.Ascii.\nDefinition string := list ascii.\n```\n\nCoq 标准库中的 ASCII 字符串也是归纳定义的，不过我们这里为了之前定义的 match relation 用 `list ascii`.\n\n> to define regex matcher over `list X` i.e. polymorphic lists.\n> we need to be able to _test equality_ for each `X` etc.\n\n\n### Rules & Derivatives.\n\nCheck paper [Regular-expression derivatives reexamined - JFP 09]() as well.\n\n`app` and `star` are the hardest ones. \n\n\n#### Let's take `app` as an example \n\n##### 1. 等价 helper \n\n```coq\nLemma app_exists : ∀(s : string) re0 re1,\n    s =~ App re0 re1 ↔ ∃s0 s1, s = s0 ++ s1 ∧ s0 =~ re0 ∧ s1 =~ re1.\n```\n\nthis _helper rules_ is written for the sake of convenience:\n- the `<-` is the definition of `MApp`.\n- the `->` is the `inversion s =~ App re0 re1`.\n\n##### 2. `App` 对于 `a :: s` 的匹配性质\n\n```coq\nLemma app_ne : ∀(a : ascii) s re0 re1,\n    a :: s =~ (App re0 re1) ↔\n    ([ ] =~ re0 ∧ a :: s =~ re1) ∨\n    ∃s0 s1, s = s0 ++ s1 ∧ a :: s0 =~ re0 ∧ s1 =~ re1.\n```\nthe second rule is more interesting. It states the _property_ of `app`:\n> App re0 re1 匹配 a::s 当且仅当  (re0 匹配空字符串 且 a::s 匹配 re1)  或  (s=s0++s1，其中 a::s0 匹配 re0 且 s1 匹配 re1)。\n\n\n这两条对后来的证明很有帮助，`app_exists` 反演出来的 existential 刚好用在 `app_ne` 中.\n> https://github.com/jiangsy/SoftwareFoundation/blob/47543ce8b004cd25d0e1769f7444d57f0e26594d/IndProp.v\n\n\n##### 3. 定义 derivative 关系\n\nthe relation _`re'` is a derivative of `re` on `a`_ is defind as follows:\n\n```coq\nDefinition is_der re (a : ascii) re' :=\n  ∀s, a :: s =~ re ↔ s =~ re'.\n```\n\n##### 4. 实现 derive\n\nNow we can impl `derive` by follwing `2`, the property.\nIn paper we have:\n\n    ∂ₐ(r · s) = ∂ₐr · s + ν(r) · ∂ₐs       -- subscriprt \"a\" meaning \"respective to a\" \n\n    where \n      ν(r) = nullable(r) ? ε : ∅ \n\nIn our Coq implementation, `nullable(r) == match_eps(r)`, \n\nSince we know that \n`∀r, ∅ · r = ∅`, \n`∀r, ε · r = r`, \nwe can be more straightforward by expanding out `v(r)`:\n\n```coq\nFixpoint derive (a : ascii) (re : @reg_exp ascii) : @reg_exp ascii :=\n...\n | App r1 r2 => if match_eps r1                            (** nullable(r) ? **)\n      then Union (App (derive a r1) r2) (derive a r2)      (**  ∂ₐr · s + ∂ₐs **)\n      else App (derive a r1) r2                            (**  ∂ₐr · s       **)\n```\n"
  },
  {
    "path": "_posts/read_sf_lf/2019-01-08-sf-lf-08-map.md",
    "content": "---\ntitle: \"「SF-LC」8 Maps\"\nsubtitle: \"Logical Foundations - Total and Partial Maps\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - LF (逻辑基础)\n  - SF (软件基础)\n  - Coq\n  - 笔记\n---\n\n> useful as env\n\nMap == Dictionary\n* building data structure.\n* use of reflection to streamline proofs.\n\nTwo flavors of maps:\n1. _total_   maps, return _default_ when lookup fails\n2. _partial_ maps, return `option` to indicate success/failure, using `None` as the default.\n\n\n## The Coq Standard Lib\n\n\nFrom now on, importing from std lib. (but should not notice much difference)\n\n```coq\nFrom Coq Require Import Arith.Arith.\nFrom Coq Require Import Bool.Bool.\nRequire Export Coq.Strings.String.\nFrom Coq Require Import Logic.FunctionalExtensionality.\nFrom Coq Require Import Lists.List.\nImport ListNotations.\n```\n\nTODO: what's the differences above? \nAnswered in Coq Intensive:\n- `Require` give access but need to use qualified name\n- `Import`  no need to use qualified name\n- `Export`  module importing me no need to use qualified name as well\n\n`String` in Coq is `list` of `Char` and `Char` is record of 8 `Bool`...\n\n\n\n## Identifiers\n\n> we need a type for the _keys_ that we use to index into our maps.\n\nIn `Lists.v` (Partial Maps):\n\n```coq\nInductive id : Type := \n  | Id (n : nat).\n```\n\nFrom now on we will use the `string` from Coq's std lib:\n\n\n```coq\nDefinition eqb_string (x y : string) : bool :=\n  if string_dec x y then true else false.\n\nCheck string_dec: (* ===> *)\n     : forall s1 s2 : string, {s1 = s2} + {s1 <> s2}\n```\n\nThe equality check fn for `string` from stdlib is `string_des`, which returns a `sumbool` type, i.e. `{x=y} + {x≠y}`.\n> which can be thought of as an __\"evidence-carrying boolean\"__. \n> Formally, an element of `sumbool` is either or\n> - a proof that two things are equal\n> - a proof that they are unequal, \n> together with a tag indicating which.\n\n\nSome properties:\n\n```coq\n(* reflexive relation *)\nTheorem eqb_string_refl : ∀s : string, true = eqb_string s s.\n\n(* functional extensionality *)\nTheorem eqb_string_true_iff : ∀x y : string, eqb_string x y = true ↔ x = y.\nTheorem eqb_string_false_iff : ∀x y : string, eqb_string x y = false ↔ x ≠ y.\n```\n\n\n## Total Maps\n\n> use _functions_, rather than lists of key-value pairs, to build maps. \n> The advantage of this representation is that it offers a more _extensional_ view of maps. 外延性\n\n> (where two maps that respond to queries in the same way will be represented as literally the same thing rather than just \"equivalent\" data structures. This, in turn, simplifies proofs that use maps.)\n\n```coq\nDefinition total_map (A : Type) := string -> A.\n\n(* empty take a default value *)\nDefinition t_empty {A : Type} (v : A) : total_map A :=\n  (fun _ => v).\n\n(* update take a key value pair *)\nDefinition t_update {A : Type} (m : total_map A)\n                    (x : string) (v : A) (* : total_map A *) :=\n  fun x' => if eqb_string x x' then v else m x'.\n```\n\nWhere is the data stored? _Closure_!\n\n\n### My Reviews on API style of ML \n\n```coq\nDefinition examplemap :=\n  t_update (t_update (t_empty false) \"foo\" true)\n           \"bar\" true.\n```\n\nsince `t_update` is defined as so called \"t-first\" style. \nReason/BuckleScript and OCaml stdlib uses this style as well:\n\n```js\nlet examplemap = \n  t_empty(false)\n  |. t_update(\"foo\", true)         /* fast pipe */\n  |. t_update(\"bar\", true) \n```\n\n```ocaml\nval add : key -> 'a -> 'a t -> 'a t\nlet examplemap = \n  Map.empty \n  |> Map.add \"foo\" true\n  |> Map.add \"bar\" true\n```\n\nOr, In Jane Street \"named-argument\" style \ne.g. [Real World OCaml](https://v1.realworldocaml.org/v1/en/html/maps-and-hash-tables.html)\n\n```ocaml\nlet examplemap = \n  Map.empty\n  |> Map.add ~key:\"foo\" ~data:true\n  |> Map.add ~key:\"bar\" ~data:true\n```\n\n### Lightweight Meta-Programming in Coq - Notation\n\nIn Coq, we can leverage some meta programming:\n\n```coq\nNotation \"'_' '!->' v\" := (t_empty v)\n  (at level 100, right associativity).\n\nNotation \"x '!->' v ';' m\" := (t_update m x v)\n  (at level 100, v at next level, right associativity).\n\nDefinition examplemap' :=\n  ( \"bar\" !-> true;\n    \"foo\" !-> true;\n    _     !-> false\n  ).\n```\n\nNoticed that the \"Map building\" is in a _reversed_ order...\n\n> Note that we don't need to define a find operation because it is just function application!\n\n```coq\nExample update_example2 : examplemap' \"foo\" = true.\nExample update_example4 : examplemap' \"bar\" = true.\nExample update_example1 : examplemap' \"baz\" = false. (* default *)\n```\n\n---\n\n## Partial Maps\n\n> we define partial maps on top of total maps. \n> A partial map with elements of type `A` is simply a total map with elements of type `option A` and default element `None`.\n\n```coq\nDefinition partial_map (A : Type) := total_map (option A).\n\nDefinition empty {A : Type} : partial_map A :=\n  t_empty None.\n\nDefinition update {A : Type} (m : partial_map A)\n           (x : string) (v : A) :=\n  (x !-> Some v ; m).\n  \nNotation \"x '⊢>' v ';' m\" := (update m x v)\n  (at level 100, v at next level, right associativity).\n\n(** hide the empty case. Since it's always [None] **)\nNotation \"x '⊢>' v\" := (update empty x v)\n  (at level 100).\n  \n(** so nice **)\nExample examplepmap :=\n  (\"Church\" ⊢> true ; \n   \"Turing\" ⊢> false).\n```\n\nwe use the \"standard\" map operator `↦` for partial map since maps in CS are usually partial.\n\n\n---\n\n## [Maps are functions](https://en.wikipedia.org/wiki/Map_(mathematics)#Maps_as_functions) \n\n\n> In many branches of mathematics, the term map is used to mean a function.\n> _partial map_ = _partial function_, \n> _total   map_ = _total   function_.\n\n\n> In category theory, \"map\" is often used as a synonym for morphism or arrow.\n\n\n> In formal logic, \"map\" is sometimes used for a functional symbol.\n\n"
  },
  {
    "path": "_posts/read_sf_lf/2019-01-09-sf-lf-09-proof-object.md",
    "content": "---\ntitle: \"「SF-LC」9 ProofObjects\"\nsubtitle: \"Logical Foundations - The Curry-Howard Correspondence \"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - LF (逻辑基础)\n  - SF (软件基础)\n  - Coq\n  - 笔记\n---\n\n> \"Algorithms are the computational content of proofs.\" —Robert Harper\n\nSo the book material is designed to be gradually reveal the facts that \n> Programming and proving in Coq are two sides of the same coin. \n\n\ne.g.\n- `Inductive` is useds for both data types and propositions.\n- `->` is used for both type of functions and logical implication.\n\nThe fundamental idea of Coq is that:\n\n> _provability_ in Coq is represented by _concrete evidence_. When we construct the proof of a basic proposition, we are actually _building a tree of evidence_, which can be thought of as a data structure.\n\ne.g.\n- implication like `A → B`, its proof will be an _evidence transformer_: a recipe for converting evidence for A into evidence for B.\n\n> Proving manipulates evidence, much as programs manipuate data.\n\n\nCurry-Howard Correspondence\n---------------------------\n\n> deep connection between the world of logic and the world of computation:\n\n    propositions             ~  types\n    proofs / evidence        ~  terms / data values \n\n\n`ev_0 : even 0`\n- `ev_0` __has type__                             `even 0`\n- `ev_0` __is a proof of__ / __is evidence for__  `even 0`\n\n`ev_SS : ∀n, even n -> even (S (S n))`\n- takes a nat `n` and evidence for `even n` and yields evidence for `even (S (S n))`.\n\nThis is _Props as Types_.\n\n\nProof Objects\n-------------\n\nProofs are data! We can see the _proof object_ that results from this _proof script_.\n\n```coq\nPrint ev_4.\n(* ===> ev_4 = ev_SS 2 (ev_SS 0 ev_0) \n             : even 4  *)\n\nCheck (ev_SS 2 (ev_SS 0 ev_0)).     (* concrete derivation tree, we r explicitly say the number tho *)\n(* ===> even 4 *)\n```\n\nThese two ways are the same in principle!\n\n\nProof Scripts\n-------------\n\n`Show Proof.`  will show the _partially constructed_ proof terms / objects.\n`?Goal` is the _unification variable_. (the hold we need to fill in to complete the proof)\n\nmore complicated in branching cases\none hole more subgoal\n\n```coq\nTheorem ev_4'' : even 4.   (*  match? (even 4) *)\nProof.\n  Show Proof.              (*  ?Goal  *)\n  apply ev_SS.\n  Show Proof.              (*  (ev_SS 2 ?Goal)  *)\n  apply ev_SS.\n  Show Proof.              (*  (ev_SS 2 (ev_SS 0 ?Goal))  *)\n  apply ev_0. \n  Show Proof.              (*  ?Goal (ev_SS 2 (ev_SS 0 ev_0))  *)\nQed.\n```\n\n> Tactic proofs are useful and convenient, but they are not essential: \n> in principle, we can always construct the required evidence by hand\n\nAgda doesn't have tactics built-in. (but also Interactive)\n\n\nQuantifiers, Implications, Functions\n------------------------------------\n\nIn Coq's _computational universe_ (where data structures and programs live), to give `->`:\n- constructors (introduced by `Indutive`)\n- functions\n\nin Coq's _logical universe_ (where we carry out proofs), to give implication:\n- constructors\n- functions!\n \n \nSo instead of writing proof scripts e.g._\n\n```coq\nTheorem ev_plus4 : ∀n, even n → even (4 + n).\nProof.\n  intros n H. simpl.\n  apply ev_SS.\n  apply ev_SS.\n  apply H.\nQed.\n```\n\nwe can give proof object, which is a _function_ here, directly!\n\n```coq\nDefinition ev_plus4' : ∀n, even n → even (4 + n) :=    (* ∀ is syntax for Pi? *)\n  fun (n : nat)    ⇒ \n  fun (H : even n) ⇒\n    ev_SS (S (S n)) (ev_SS n H).\n\n\nDefinition ev_plus4'' (n : nat) (H : even n)           (* tricky: implicitly `Pi` when `n` get mentioned?  *)\n                    : even (4 + n) :=\n  ev_SS (S (S n)) (ev_SS n H).\n```\n\ntwo interesting facts:\n1. `intros x` corresponds to `λx.` (or `Pi x.`??)\n2. `apply` corresponds to...not quite function application... but more like _filling the hole_.\n3. `even n` mentions the _value_ of 1st argument `n`. i.e. _dependent type_!\n\n\nRecall Ari's question in \"applying theorem as function\" e.g. `plus_comm` \nwhy we can apply value in type-level fun.\nbecuz of dependent type.\n\nNow we call them `dependent type function`\n\n\n\n### `→` is degenerated `∀` (`Pi`)\n\n> Notice that both implication (`→`) and quantification (`∀`) correspond to functions on evidence. \n> In fact, they are really the same thing: `→` is just a shorthand for a degenerate use of `∀` where there is no dependency, i.e., no need to give a name to the type on the left-hand side of the arrow:\n\n```coq\n  ∀(x:nat), nat \n= ∀(_:nat), nat \n= nat → nat\n\n  ∀n, ∀(E : even n), even (n + 2).\n= ∀n, ∀(_ : even n), even (n + 2).\n= ∀n, even n → even (n + 2).\n```\n\n> In general, `P → Q` is just syntactic sugar for `∀ (_:P), Q`.\n\nTaPL also mention this fact for `Pi`.\n\n\nQ&A - Slide 15\n--------------\n\n1. `∀ n, even n → even (4 + n)`. (`2 + n = S (S n)`)\n\n\n\n\nProgramming with Tactics.\n-------------------------\n\nIf we can build proofs by giving explicit terms rather than executing tactic scripts, \nyou may be wondering whether we can _build programs using tactics_? Yes!\n\n```coq\nDefinition add1 : nat → nat.\n  intro n.\n  Show Proof.      \n(** \nthe goal (proof state):\n    \n    n : nat\n    =======\n    nat\n    \nthe response:\n\n    (fun n : nat => ?Goal) \n    \nWhat is really interesting here, is that the premies [n:nat] is actually the arguments!\nagain, the process of applying tactics is _partial application_\n**)\n\n  apply S.\n  Show Proof.      \n(** \n    (fun n : nat => S ?Goal) \n**)\n  apply n. \nDefined.\n\nPrint add1.\n(* ==> add1 = fun n : nat => S n\n            : nat -> nat *)\n```\n\n> Notice that we terminate the Definition with a `.` rather than with `:=` followed by a term.\n> This tells Coq to enter _proof scripting mode_ (w/o `Proof.`, which did nothing)\n\n> Also, we terminate the proof with `Defined` rather than `Qed`; this makes the definition _transparent_ so that it can be used in computation like a normally-defined function\n> (`Qed`-defined objects are _opaque_ during computation.).\n\n`Qed` make things `unfold`able, \nthus `add 1` ends with `Qed` is not computable...\n(becuz of not even `unfold`able thus computation engine won't deal with it)\n\n> Prof.Mtf: meaning \"we don't care about the details of Proof\"\n\nsee as well [Smart Constructor](https://wiki.haskell.org/Smart_constructors)\n\n\n> This feature is mainly useful for writing functions with dependent types\n\nIn Coq      - you do as much as ML/Haskell when you can...?\nUnlike Agda - you program intensively in dependent type...?\n\nWhen Extracting to OCaml...Coq did a lot of `Object.magic` for coercion to bypass OCaml type system. (Coq has maken sure the type safety.)\n\n\nLogical Connectives as Inductive Types\n--------------------------------------\n\n> Inductive definitions are powerful enough to express most of the connectives we have seen so far. \n> Indeed, only universal quantification (with implication as a special case) is built into Coq; \n> all the others are defined inductively. \nWow...\n\n> CoqI: What's Coq logic? Forall + Inductive type (+ coinduction), that's it.\n\n### Conjunctions\n\n```coq\nInductive and (P Q : Prop) : Prop :=\n| conj : P → Q → and P Q.\n\nPrint prod.\n(* ===>\n   Inductive prod (X Y : Type) : Type :=\n   | pair : X -> Y -> X * Y. *)\n```\n\nsimilar to `prod` (product) type... more connections happening here.\n\n> This similarity should clarify why `destruct` and `intros` patterns can be used on a conjunctive hypothesis. \n\n> Similarly, the `split` tactic actually works for any inductively defined proposition with exactly one constructor\n(so here, `apply conj`, which will match the conclusion and generate two subgoal from assumptions )\n\nA _very direct_ proof:\n\n```coq\nDefinition and_comm'_aux P Q (H : P ∧ Q) : Q ∧ P :=\n  match H with\n  | conj HP HQ ⇒ conj HQ HP\n  end.\n```\n\n\n\n### Disjunction\n\n```coq\nInductive or (P Q : Prop) : Prop :=\n| or_introl : P → or P Q\n| or_intror : Q → or P Q.\n```\n\nthis explains why `destruct` works but `split` not..\n\n\nQ&A - Slide 22 + 24\n-------------------\n\nBoth Question asked about what's the type of some expression\n\n```coq\nfun P Q R (H1: and P Q) (H2: and Q R) ⇒\n    match (H1,H2) with\n    | (conj _ _ HP _, conj _ _ _ HR) ⇒ conj P R HP HR\n    end.\n\nfun P Q H ⇒\n    match H with\n    | or_introl HP ⇒ or_intror Q P HP\n    | or_intror HQ ⇒ or_introl Q P HQ\n    end.\n```\nBut if you simply `Check` on them, you will get errors saying:\n`Error: The constructor conj (in type and) expects 2 arguments.` or \n`Error: The constructor or_introl (in type or) expects 2 arguments.`.\n\n\n### Coq Magics, \"Implicit\" Implicit and Overloading??\n\nSo what's the problem?\nWell, Coq did some magics...\n\n```coq\nPrint and.\n(* ===> *)\nInductive and (A B : Prop) : Prop :=  conj : A -> B -> A /\\ B\nFor conj: Arguments A, B are implicit\n```\n\nconstructor `conj` has implicit type arg w/o using `{}` in `and` ...\n\n```coq\nInductive or (A B : Prop) : Prop :=\n    or_introl : A -> A \\/ B | or_intror : B -> A \\/ B\n\nFor or_introl, when applied to no more than 1 argument:\n  Arguments A, B are implicit\nFor or_introl, when applied to 2 arguments:\n  Argument A is implicit\nFor or_intror, when applied to no more than 1 argument:\n  Arguments A, B are implicit\nFor or_intror, when applied to 2 arguments:\n  Argument B is implicit\n```\n\nthis is even more bizarre...\nconstructor `or_introl` (and `or_intror`) are _overloaded_!! (WTF)\n\n\nAnd the questions're still given as if they're inside the modules we defined our plain version of `and` & `or` (w/o any magics), thus we need `_` in the positions we instantiate `and` & `or` so Coq will infer.\n\n\n\n### Existential Quantification\n\n> To give evidence for an existential quantifier, we package a witness `x` together with a proof that `x` satisfies the property `P`:\n\n```coq\nInductive ex {A : Type} (P : A → Prop) : Prop :=\n| ex_intro : ∀x : A, P x → ex P.\n\nCheck ex.                    (* ===> *) : (?A -> Prop) -> Prop \nCheck even.                  (* ===> *) : nat -> Prop  (* ?A := nat  *)\nCheck ex even.               (* ===> *) : Prop \nCheck ex (fun n => even n)   (* ===> *) : Prop     (* same *)\n```\n\none interesting fact is, _outside_ of our module, the built-in Coq behaves differently (_magically_):\n\n```coq\nCheck ev.                    (* ===> *) : ∀ (A : Type), (A -> Prop) -> Prop\nCheck even.                  (* ===> *) : nat -> Prop  (* A := nat  *)\nCheck ex (fun n => even n)   (* ===> *) : ∃ (n : nat) , even n : Prop  (* WAT !? *)\n```\n\nA example of explicit proof object (that inhabit this type):\n\n```coq\nDefinition some_nat_is_even : ∃n, even n :=\n  ex_intro even 4 (ev_SS 2 (ev_SS 0 ev_0)).\n```\n\nthe `ex_intro` take `even` first then `4`...not sure why the order becomes this... \n\n```coq\nCheck (ex_intro).            (* ===> *) : forall (P : ?A -> Prop) (x : ?A), P x -> ex P\n```\n\nTo prove `ex P`, given a witness `x` and a proof of `P x`. This desugar to `∃ x, P x`\n\n- the `P` here, is getting applied when we define prop `∃ x, P x`.\n- but the `x` is not mentioned in type constructor...so it's a _existential type_.\n  - I don't know why languages (including Haskell) use `forall` for _existential_ tho.\n\n`exists` tactic = applying `ex_intro`\n\n\n\n### True and False\n\n```coq\nInductive True : Prop :=\n  | I : True.\n\n(* with 0 constructors, no way of presenting evidence for False *)\nInductive False : Prop := .\n```\n\n\nEquality\n--------\n\n```coq\nInductive eq {X:Type} : X → X → Prop :=\n| eq_refl : ∀x, eq x x.\n\nNotation \"x == y\" := (eq x y)\n                    (at level 70, no associativity)\n                    : type_scope.\n```\n\n\n> given a set `X`, it defines a _family_ of propositions \"x is equal to y,\", _indexed by_ pairs of values (x and y) from `X`.\n\n> Can we also use it to construct evidence that `1 + 1 = 2`? \n> Yes, we can. Indeed, it is the very same piece of evidence!\n\n> The reason is that Coq treats as \"the same\" any two terms that are convertible according to a simple set of computation rules.\n\nnothing in the unification engine but we relies on the _reduction engine_.\n\n> Q: how much is it willing to do?  \n> Mtf: just run them! (since Coq is total!)\n\n```coq\nLemma four: 2 + 2 == 1 + 3.\nProof.\n  apply eq_refl.\nQed.\n```\n\nThe `reflexivity` tactic is essentially just shorthand for `apply eq_refl`.\n\n\nSlide Q & A\n-----------\n\n- (4) has to be applicable thing, i.e. lambda, or \"property\" in the notion! \n\nIn terms of provability of `reflexivity`\n\n```coq\n(fun n => S (S n)) = (fun n => 2 + n)          (* reflexivity *)\n(fun n => S (S n)) = (fun n => n + 2)          (* rewrite add_com *)\n```\n\n### Inversion, Again\n\n> We've seen inversion used with both equality hypotheses and hypotheses about inductively defined propositions. Now that we've seen that these are actually the same thing\n\nIn general, the `inversion` tactic...\n\n1. take hypo `H` whose type `P` is inductively defined\n2. for each constructor `C` in `P`\n   1. generate new subgoal (assume `H` was built with `C`)\n   2. add the arguments (i.e. evidences of premises) of `C` as extra hypo (to the context of subgoal)\n   3. (apply `constructor` theorem), match the conclusion of `C`, calculates a set of equalities (some extra restrictions)\n   4. adds these equalities\n   5. if there is contradiction, `discriminate`, solve subgoal.\n\n\n### Q\n\n> Q: Can we write `+` in a communitive way?  \n> A: I don't believe so.\n\n\n[Ground truth](https://en.wikipedia.org/wiki/Ground_truth)\n - provided by direct observation (instead of inference)\n\n[Ground term](https://en.wikipedia.org/wiki/Ground_expression#Ground_terms) \n - that does not contain any free variables.\n\nGroundness\n - 根基性?\n\n> Weird `Axiomness` might break the soundness of generated code in OCaml...\n\n\n\n\n\n"
  },
  {
    "path": "_posts/read_sf_lf/2019-01-10-sf-lf-10-ind-principle.md",
    "content": "---\ntitle: \"「SF-LC」10 IndPrinciples\"\nsubtitle: \"Logical Foundations - Induction Principles\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - LF (逻辑基础)\n  - SF (软件基础)\n  - Coq\n  - 笔记\n---\n\n\nBasic\n-----\n\n> 每次我们使用 `Inductive` 来声明数据类型时，Coq 会自动为这个类型生成 _归纳原理_。\n> Every time we declare a new `Inductive` datatype, Coq automatically generates an _induction principle_ for this type. \n\n\n自然数的归纳原理:\n\n```coq\nCheck nat_ind. :\n\n∀ P : nat → Prop,\n  P 0  →\n  (∀ n : nat, P n -> P (S n)) →\n  ∀ n : nat, P n\n```\n\nwritten as inference rule:\n\n                        P 0\n      ∀ n : nat, P n -> P (S n)\n      -------------------------\n      ∀ n : nat,        P n\n\n\n> `induction` tactic is wrapper of `apply t_ind`\n\n\n> Coq 为每一个 `Inductive` 定义的数据类型生成了归纳原理，包括那些非递归的\n> Coq generates induction principles for every datatype defined with `Inductive`, including those that aren't recursive. \n\n> 尽管我们不需要使用归纳来证明非递归数据类型的性质\n> Although of course we don't need induction to prove properties of non-recursive datatypes. (`destruct` would be sufficient)\n\n> 归纳原理的概念仍然适用于它们： 它是一种证明一个对于这个类型所有值都成立的性质的方法。\n> the idea of an induction principle still makes sense for them: it gives a way to prove that a property holds for all values of the type.\n\n\n### Non-recursive\n\n```coq\nInductive yesno : Type :=\n  | yes\n  | no.\n\nCheck yesno_ind. :\nyesno_ind : ∀ P : yesno → Prop,\n  P yes  →\n  P no   →\n  ∀ y : yesno, P y \n```\n\n                 P yes \n                 P no\n    ------------------\n    ∀ y : yesno, P y \n\n\n### Structural-Recursive\n\n```coq\nInductive natlist : Type :=\n  | nnil\n  | ncons (n : nat) (l : natlist).\n\nCheck natlist_ind. :\nnatlist_ind : ∀ P : natlist → Prop,\n  P nnil  →\n  (∀ (n : nat) (l : natlist), P l -> P (ncons n l)) →\n  ∀ l : natlist, P l \n```\n\n                                      P nnil \n    ∀ (n : nat) (l : natlist), P l -> P (ncons n l)\n    -----------------------------------------------\n    ∀ l : natlist,                    P l \n\n\n`P` only need to fullfill `l : the_type` but not `n:nat` since we are proving property of `the_type`.\n\n\n### The Pattern\n\n> These generated principles follow a similar pattern. \n- induction on each cases \n- proof by exhaustiveness?\n\n```coq\nInductive t : Type := \n  | c1 (x1 : a1) ... (xn : an)\n  ...\n  | cn ...\n\nt_ind : ∀P : t → Prop,\n              ... case for c1 ... →\n              ... case for c2 ... → ...\n              ... case for cn ... →\n              ∀n : t, P n\n```\n\n对于 `t` 的归纳原理是又所有对于 `c` 的归纳原理所组成的: （即所有 case 成立)\n\n对于 `c` 的归纳原理则是\n> 对于所有的类型为 `a1...an` 的值 `x1...xn`，如果 `P` 对每个 归纳的参数（每个具有类型 `t` 的 `xi`）都成立，那么 `P` 对于 `c x1 ... xn` 成立”\n\n每个具有类型 `t` 的参数的地方即发生了「递归」与「子结构」，归纳假设 = 「对子结构成立」.\n\n\n\n\n\nPolymorphism\n------------\n\n接下来考虑多态列表：\n\n\n```coq\n(* in ADT syntax *)\nInductive list (X:Type) : Type :=\n  | nil \n  | cons (x : X) (l': list X) \n\n(* in GADT syntax *)\nInductive list (X:Type) : Type :=\n  | nil : list X\n  | cons : X → list X → list X.\n```\n\n> here, the whole def is _parameterized_ on a `set X`: that is, we are defining a _family_ of inductive types `list X`, one for each `X`.\n\n这里，整个定义都是被集合 `X` _参数化_的： \n也即，我们定义了一个族 `list : X -> Type`, 对于每个 `X`，我们都有一个对应的_项_: `list X`, which is a `Type`, 可写作 `list X : Type`.\n\n\n> `list_ind` can be thought of as a polymorphic function that, \n> when applied to a type `X`, gives us back an induction principle specialized to the type `list X`.\n\n因此，其归纳定理 `list_ind` 是一个被 `X` 参数化多态的函数。\n当应用 `X : Type` 时，返回一个特化在 `list X : Type` 上的归纳原理\n\n\n```coq\nlist_ind : ∀(X : Type) (P : list X → Prop),\n    P [] →\n    (∀(x : X) (l : list X), P l → P (x :: l)) →\n    ∀l : list X, P l\n```\n\n    ∀(X : Type), {\n\n                               P []                   -- base structure holds\n        ∀(x : X) (l : list X), P l → P (x :: l)       -- sub-structure holds -> structure holds\n        ---------------------------------------\n        ∀l : list X,           P l                    -- all structure holds\n\n    }\n\n\n\nInduction Hypotheses 归纳假设\n----------------------------\n\n\n>  The induction hypothesis is the _premise_ of this latter implication \n> — the assumption that `P` holds of `n'`, which we are allowed to use in proving that `P` holds for `S n'`.\n\n_归纳假设就是 `P n' -> P (S n')` 这个蕴含式中的前提部分_\n使用 `nat_ind` 时需要显式得用 `intros n IHn` 引入，于是就变成了 proof context 中的假设.\n\n\n\n\n\nMore on the `induction` Tactic\n------------------------------\n\n### \"Re-generalize\" 重新泛化\n\nNoticed that in proofs using `nat_ind`, we need to keep `n` generailzed. \nif we `intros` particular `n` first then `apply nat_ind`, it won't works...\n\nBut we could `intros n. induction n.`, that's `induction` tactic internally \"re-generalize\" the `n` we perform induction on.\n\n\n### Automatic `intros` i.e. specialize variables before the variable we induction on\n\nA canonical case is `induction n` vs `induction m` on theorem `plus_comm'' : ∀n m : nat, n + m = m + n.`.\nto keep a var generial...we can either change variable order under `∀`, or using `generalize dependent`.\n\n\n\n\n\nInduction Principles in Prop\n----------------------------\n\n### 理解依赖类型的归纳假设 与 Coq 排除证据参数的原因\n\n除了集合 `Set`，命题 `Prop` 也可以是归纳定义与 `induction` on 得.\n难点在于：_Inductive Prop_ 通常是 dependent type 的，这里会带来复杂度。\n\n考虑命题 `even`:\n\n```coq\n Inductive even : nat → Prop :=\n  | ev_0 : even 0\n  | ev_SS : ∀n : nat, even n → even (S (S n)).\n```\n\n我们可以猜测一个最 general 的归纳假设：\n\n```coq\nev_ind_max : ∀ P : (∀n : nat, even n → Prop),\n  P O ev_0 →\n  (∀(m : nat) (E : even m), P m E → P (S (S m)) (ev_SS m E)) →\n  ∀(n : nat) (E : even n), P n E\n```\n\n即:\n\n\n                                       P 0 ev_0                    -- base \n      ∀(m : nat) (E : even m), P m E → P (S (S m)) (ev_SS m E)     -- sub structure -> structure\n      --------------------------------------------------------\n      ∀(n : nat) (E : even n),         P n E                       -- all structure\n\n\n注意这里:\n\n1. `even` is _indexed_ by nat `n` (对比 `list` is _parametrized_ by `X`)\n  - 从族的角度:  `even : nat -> Prop`, a family of `Prop` indexed by `nat`\n  - 从实体角度: 每个 `E : even n` 对象都是一个 evidence that _particular nat is even_.\n\n2. 要证的性质 `P` is parametrized by `E : even n` 也因此连带着 by `n`. 也就是 `P : (∀n : nat, even n → Prop)`  (对比 `P : list X → Prop`)\n  - 所以其实关于 `even n` 的性质是同时关于数字 `n` 和证据 `even n` 这两件事的.\n  \n因此 `sub structure -> structure` 说得是：\n> whenever `n` is an even number and `E` is an evidence of its evenness, if `P` holds of `n` and `E`, then it also holds of `S (S n)` and `ev_SS n E`.\n> 对于任意数字 `n` 与证据 `E`，如果 `P` 对 `n` 和 `E` 成立，那么它也对 `S (S n)` 和 `ev_SS n E` 成立。\n\n\n\n然而，当我们 `induction (H : even n)` 时，我们通常想证的性质并不包括「证据」，而是「满足该性质的这 `Type` 东西」的性质, \n比如:\n1. `nat` 上的一元关系 (性质)    证明 `nat` 的性质          :  `ev_even : even n → ∃k, n = double k`\n2. `nat` 上的二元关系           证明 `nat` 上的二元关系    :  `le_trans : ∀m n o, m ≤ n → n ≤ o → m ≤ o` \n3. 二元关系 `reg_exp × list T` 证明 二元关系 `reg_exp × T`:  `in_re_match : ∀T (s : list T) (x : T) (re : reg_exp), s =~ re → In x s → In x (re_chars re).` \n都是如此，\n\n因此我们也不希望生成的归纳假设是包括证据的...\n原来的归纳假设：\n\n      ∀P : (∀n : nat, even n → Prop),\n      ... →\n      ∀(n : nat) (E : even n), P n E\n      \n可以被简化为只对 `nat` 参数化的归纳假设：\n\n      ∀P : nat → Prop,\n      ... →\n      ∀(n : nat) (E: even n), P n\n      \n\n因此 coq 生成的归纳原理也是不包括证据的。注意 `P` 丢弃了参数 `E`:\n\n```coq\neven_ind : ∀ P : nat -> Prop,\n  P 0 →\n  (∀ n : nat, even n -> P n -> P (S (S n))) →\n  ∀ n : nat, even n -> P n *)\n```\n\n用人话说就是：\n1. P 对 0 成立，\n2. 对任意 n，如果 n 是偶数且 P 对 n 成立，那么 P 对 S (S n) 成立。\n=> P 对所有偶数成立\n\n\n### \"General Parameter\"\n\n```coq\nInductive le : nat → nat → Prop :=\n  | le_n : ∀ n,               le n n\n  | le_S : ∀ n m, (le n m) → (le n (S m)).\n```\n\n```coq\nInductive le (n:nat) : nat → Prop :=\n  | le_n                : le n n\n  | le_S m (H : le n m) : le n (S m).\n```\n\n两者虽然等价，但是共同的 `∀ n` 可以被提升为 typecon 的参数, i.e. \"General Parameter\" to the whole definition.\n\n其生成的归纳假设也会不同: (after renaming)\n\n```coq\nle_ind : ∀ P : nat -> nat -> Prop,\n  (∀ n : nat, P n n) ->\n  (∀ n m : nat, le n m -> P n m -> P n (S m)) ->\n  ∀ n m : nat, le n m -> P n m \n```\n\n```coq\nle_ind : ∀ (n : nat) (P : nat -> Prop),\n  P n ->\n  (∀ m : nat, n <= m -> P m -> P (S m)) ->\n  ∀ m : nat, n <= m -> P m \n```\n\nThe 1st one looks more symmetric but 2nd one is easier (for proving things).\n\n"
  },
  {
    "path": "_posts/read_sf_lf/2019-01-11-sf-lf-11-rel.md",
    "content": "---\ntitle: \"「SF-LC」11 Rel\"\nsubtitle: \"Logical Foundations - Properties of Relations\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - LF (逻辑基础)\n  - SF (软件基础)\n  - Coq\n  - 笔记\n---\n\n> relation 与injective/surjective/bijective function 等相关的知识在 `5. Tactics` 里，为了避免每次都要 `grep` 我在这里写一下。\n\n\nRelations\n---------\n\n\n### Recalling [Relation](https://en.wikipedia.org/wiki/Finitary_relation)\n\nfrom FCT/TAPL/Wiki...\n> a possible connection between the components of a k-tuple.\n\nI have been long confused with _Unary Relations vs. Binary Relation on the Same Set (homogeneous relation)_\nI thought they were same...but turns out they are totally different!\n\n\n#### Unary/1-place relation is __Predicate__ or __Property__! \n\nEither defined via set `X ⊆ P` or `x ∈ P`,\nor defined via function `P : X -> Bool` or `P : X -> {⊥, ⊤}`.\n(usually used in Math. Logic)\n\nProperty = Indicator Fn = characteristic Fn = Boolean Predicate Fn = Predicate\n- <https://en.wikipedia.org/wiki/Property_(mathematics)>\n- <https://en.wikipedia.org/wiki/Indicator_function>\n\n\n#### [Binary Relation/2-place relation](https://en.wikipedia.org/wiki/Binary_relation) \n\nDefined via two sets : `R ⊆ X × Y` or `x, y ∈ R` or `xRy`.  (where `x ∈ X, y ∈ Y`.)\nor via function `R: X × Y -> Bool`.\n\n##### [Homogeneous Relation 同类（的）关系](https://en.wikipedia.org/wiki/Binary_relation#Homogeneous_relation)\n\nSpecifically! when `X = Y`, is called a _homogeneous relation_: \n\nNoticed that we are still concerning relations of __2 elements__!!, but they are from the same Set!\n(while 1-place relation concerning only 1 element.)\n\n    R ⊆ X × X\n    xRy where x ∈ X, y ∈ X\n\nit's written/spoken _Binary_ relation __on/over__ Set `X`. \nProperties e.g. _reflexive, symmetric, transitive_, are all properties of \"Homogeneous Relation\"!\n\n\n\n### Back to Coq\n\n\"relation\" is a general idea. but in Coq standard lib it means \"binary relation on _a_ set X\"\n> Coq `identifier` relation will always refer to a binary relation between some set and itself.\n\nit's defined as _a family of Prop parameterized by two elements of `X`_:\n\n```coq\nDefinition relation (X: Type) := X → X → Prop.\n\nCheck le : nat -> nat -> Prop.\nCheck le : relation nat.\n```\n\n\n\n\nBasic Properties\n----------------\n\n> ways to classifying relations.\n> so theorems can be proved generically about certain sorts of relations\n\nIt's pretty fun to see all mathematical things defined in Coq!\n(much more constructive)\n\n\n### [Partial Function](https://en.wikipedia.org/wiki/Partial_function)\n\n> function is defined as _a special kind of binary relation_. \n\n```coq\nDefinition partial_function {X: Type} (R: relation X) :=\n  ∀x y1 y2 : X, R x y1 → R x y2 → y1 = y2.\n```\n\nmeaning that foreach input `x ∈ X`, there is a _unique_ `y ∈ Y` corresponded.\n\nBut this only establish a _partial function_. \nbecause it doesn't say anything about _totality_,\nto define _total function_, we require `f` map every `x ∈ X`. \n\n- [Total \"Relation\"](https://en.wikipedia.org/wiki/Connex_relation)\n\n    ∀x ∀y (x ∈ X ∧ y ∈ X) ⇒ (xRy ∨ yRx).\n\ntotally different with _total function_ but ask the binary relation holds between every pair.\n\n\n### Reflexive\n\n```coq\nDefinition transitive {X: Type} (R: relation X) :=\n  ∀a b c : X, (R a b) → (R b c) → (R a c).\n```\n\n### Transitive\n\n```coq\nDefinition transitive {X: Type} (R: relation X) :=\n  ∀a b c : X, (R a b) → (R b c) → (R a c).\n```\n\n### Symmetric & Antisymmetric\n\n```coq\nDefinition symmetric {X: Type} (R: relation X) :=\n  ∀a b : X, (R a b) → (R b a).\n  \nDefinition antisymmetric {X: Type} (R: relation X) :=\n  ∀a b : X, (R a b) → (R b a) → a = b.\n```\n\n#### Antisymmetric vs Asymmetric vs Non-symmetric (反对称 vs. 非对称 vs. 不-对称)\n\nA relation is __asymmetric__ if and only if it is both antisymmetric and irreflexive\ne.g. `<=` is neither symmetric nor asymmetric, but it's antisymmetric...\n反对称: 可以自反 (只能 reflexive 时对称) `<=`\n非对称: 不能自反 `<`\n不对称: 不是对称 \n\n\n\n### Equivalence\n\n```coq\nDefinition equivalence {X:Type} (R: relation X) :=\n  (reflexive R) ∧ (symmetric R) ∧ (transitive R).\n```\n\n\n### Partial Orders \n\nA partial order under which _every pair_ of elements is _comparable_ is called a __total order__ or __linear order__\nIn the Coq standard library it's called just `order` for short:\n\n```coq\nDefinition order {X:Type} (R: relation X) :=\n  (reflexive R) ∧ (antisymmetric R) ∧ (transitive R).\n```\n\n\n### Preorders \n\na.k.a quasiorder\n\nThe _subtyping_ relations are usually preorders.\n> (TAPL p185) because of the record permutation rule...there are many pairs of distinct types where each is a subtype of the other.\n\n```coq\nDefinition preorder {X:Type} (R: relation X) :=\n  (reflexive R) ∧ (transitive R).\n```\n\n\n\n\n\nReflexive, Transitive Closure\n-----------------------------\n\n> [Closure](https://en.wikipedia.org/wiki/Closure_(mathematics)#Binary_relation_closures)\n> Closure can be considered as [Operations on bin-rel](https://en.wikipedia.org/wiki/Binary_relation#Operations_on_binary_relations)\n\nAs properties such as _reflexive, transitive_, \nthe __blah blah Closure__ are only talking about \"homogeneous relations\" i.e., Relation on a SINGLE set.\n\n\n### [Reflexive Closure](https://en.wikipedia.org/wiki/Reflexive_closure)\n\nDef. smallest reflexive relation on `X` containing `R`.\n\nOperationally, as a `=` operator on a binary relation `R`:\n\n    R⁼ = R ∪ { (x, x) | x ∈ X }\n\nand this obviously satisfy `R⁼ ⊇ R`.\n\n\n### [Transitive Closure](https://en.wikipedia.org/wiki/Transitive_closure)\n\nDef. smallest transitive relation on `X` containing `R`.\n\nOperationally, as a `+` operator on a binary relation `R`:\n\n    R+ = R ∪ { (x1,xn) | n > 1 ∧ (x1,x2), ..., (xn-1,xn) ∈ R }\n\nWe can also constructively and inductively definition using `R^i` where `i = i-transitivity away`.\n\n\n### Reflexive, Transitive Closure\n\n    R* = R⁼ ∪ R+\n\n\n\n### Why is it useful?\n\n> The idea is that _a relation is extended_ s.t. \n>_the derived relation has the (reflexsive and) transitive property._ -- Prof. Arthur\n\n> e.g.\n> the \"descendant\" relation is the transitive closure of the \"child\" relation, \n> the \"derives-star (⇒⋆)\" relation is the reflexive-transitive closure of the \"derives (⇒)\" relation.\n> the \"ε-closure\" relation is the reflexive-transitive closure of the \"ε-transition\" relation.\n> the \"Kleene-star (Σ⋆)\" relation is the reflexive-transitive closure of the \"concatentation\" relation.\n\nAnother way is to think them as \"set closed under some operation\".\n\n\n### Back to Coq\n\n```coq\nInductive clos_refl_trans {A: Type} (R: relation A) : relation A :=\n  | rt_step x y (H : R x y) : clos_refl_trans R x y        (** original relation **)\n  | rt_refl x : clos_refl_trans R x x                      (** reflexive  xRx              **)\n  | rt_trans x y z                                         (** transitive xRy ∧ yRz → xRz  **)\n        (Hxy : clos_refl_trans R x y)\n        (Hyz : clos_refl_trans R y z) :\n        clos_refl_trans R x z.\n```\n\nThe above version will generate 2 IHs in `rt_trans` case. (since the proof tree has 2 branches).\n\nHere is a better \"linked-list\"-ish one. (we will exclusively use this style)\n\n```coq\nInductive clos_refl_trans_1n {A : Type} (R : relation A) (x : A) : A → Prop :=\n  | rt1n_refl : clos_refl_trans_1n R x x\n  | rt1n_trans (y z : A)\n      (Hxy   : R x y) \n      (Hrest : clos_refl_trans_1n R y z) :\n      clos_refl_trans_1n R x z.\n```\n\nIn later chapter, we will define a decorator `multi` that can take any binary relation on a set and return its closure relation:\n\n```coq\nInductive multi (X : Type) (R : relation X) : relation X :=\n  | multi_refl : forall x     : X,                         multi R x x\n  | multi_step : forall x y z : X, R x y -> multi R y z -> multi R x z\n```\n\nWe name it `step`, standing for _doing one step of this relation_, and then we still have the rest (sub-structure) satisfied the closure relation.\n"
  },
  {
    "path": "_posts/read_sf_lf/2019-01-12-sf-lf-12-imp.md",
    "content": "---\ntitle: \"「SF-LC」12 Imp\"\nsubtitle: \"Logical Foundations - Simple Imperative Programs\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - LF (逻辑基础)\n  - SF (软件基础)\n  - Coq\n  - 笔记\n---\n\n\n```pascal\nZ ::= X;;\nY ::= 1;;\nWHILE ~(Z = 0) DO\n  Y ::= Y * Z;;\n  Z ::= Z - 1\nEND\n```\n\nA weird convention through out all IMP is:\n- `a-`: arith \n- `b-`: bool\n- `c-`: command\n\n\n\nArithmetic and Boolean Expression\n---------------------------------\n\n### Abstract Syntax\n\n```coq\na ::=\n    | nat\n    | a + a\n    | a - a\n    | a * a\nb ::= \n    | true\n    | false\n    | a = a\n    | a ≤ a\n    | ¬b\n    | b && b\n```\n\n```coq\nInductive aexp : Type :=\n  | ANum (n : nat)\n  | APlus (a1 a2 : aexp)\n  | AMinus (a1 a2 : aexp)\n  | AMult (a1 a2 : aexp).\nInductive bexp : Type :=\n  | BTrue\n  | BFalse\n  | BEq (a1 a2 : aexp)\n  | BLe (a1 a2 : aexp)\n  | BNot (b : bexp)\n  | BAnd (b1 b2 : bexp).\n```\n\n### Evaluation\n\nTODO: is this considered as \"denotational semantics\"?\n\n```coq\nFixpoint aeval (a : aexp) : nat :=\n  match a with\n  | ANum n ⇒ n\n  | APlus a1 a2 ⇒ (aeval a1) + (aeval a2)\n  | AMinus a1 a2 ⇒ (aeval a1) - (aeval a2)\n  | AMult a1 a2 ⇒ (aeval a1) * (aeval a2)\n  end.\nFixpoint beval (b : bexp) : bool :=\n  match b with\n  | BTrue ⇒ true\n  | BFalse ⇒ false\n  | BEq a1 a2 ⇒ (aeval a1) =? (aeval a2)\n  | BLe a1 a2 ⇒ (aeval a1) <=? (aeval a2)\n  | BNot b1 ⇒ negb (beval b1)\n  | BAnd b1 b2 ⇒ andb (beval b1) (beval b2)\n  end.\n```\n\nSupposed we have a `Fixpoint optimize_0plus (a:aexp) : aexp`\n```coq\nTheorem optimize_0plus_sound: ∀a,\n  aeval (optimize_0plus a) = aeval a.\n```\n\nDuring the proof, many cases of `destruct aexp` are similar!\nRecursive cases such as `APlus, AMinus, AMult` all require duplicated `IH` application.\n\n> From Coq Intensive:\nwhen we `simpl` on `APlus` case. it's not \"simplified\" but give us a pattern matching.\nThat's a hint that we need to furthur case analysis by `destruct n` as `0` case or `_` case.\n\n\n\n\n\n\nCoq Automation\n--------------\n\n### Tacticals\n\n> \"higher-order tactics\".\n\n#### `try T` and `;` tacticals\n\n> if `T` fail, `try T` successfully does nothing at all\n\n> `T;T'` : performs `T'` on each subgoal generated by `T`.\n\nSuper blindly but useful: (only leave the \"interesting\" one.)\n\n```coq\ninduction a;\n    (* Most cases follow directly by the IH... *)\n    try (simpl; rewrite IHa1; rewrite IHa2; reflexivity).\n    (* ... or are immediate by definition *)\n    try reflexivity.\n```\n\n`.` is the atomic \n`;` cannot be stepped into...\n\n#### `T; [T1 | T2 | ... | Tn]` tacticals\n\n> general form or `;`\n> `T;T'` is shorthand for: `T; [T' | T' | ... | T']`.\n\n\n#### `repeat` tacticals\n\n```coq\nTheorem In10 : In 10 [1;2;3;4;5;6;7;8;9;10].\nProof.\n  repeat (try (left; reflexivity); right). Qed.\n```\n\n- stop when it fails\n- always succeeds, then loop forever! e.g. `repeat simpl`\n\n> This does not affect Coq's logical consistency, \n> construction process diverges means we have failed to construct a proof, not that we have constructed a wrong one.\n\n\n### Defining New Tactic Notations\n\n- `Tactic Notation`: syntax extension for tactics (good for simple _macros_)\n\n```coq\nTactic Notation \"simpl_and_try\" tactic(c) :=\n  simpl; try c.\n```\n\n- `Ltac`: scripting language for tactics (good for more sophisticated proof engineering)\n- OCaml tactic scripting API (for wizards)\n\n### The `omega` Tactic\n\n> _Presburger arithmetic_\n- arith, equality, ordering, logic connectives\n- `O(doubly expontential)` \n\n  \n### A Few More Handy Tactics\n\n- `clear H`\n- `subst x`, `subst`\n- `rename ... into ...`  (change auto-generated name that we don't like...)\n\nthe below three are very useful in Coq Automation (w/ `try T; T'`) \n\n- `assumption`\n- `contradiction`\n- `constructor`  (try to `apply` all constructors. \n                  Problem: might have multiple constructors applicable but some fail)\n\n\n\n\n\n\nEvaluation as a Relation\n------------------------\n\nDefined as Binary relation on `aexp × nat`. \nExactly _Big Step / Structural Operational Semantics_.\n\nMore flexible than `Fixpoint` (computation, or _Denotational_). \n...Since we can operate on `Inductive` as data I guess? \n...and we can also `induction` on the relation.\n...and when things getting more and more \"un-computable\" _(see below)_.\n\n> 译注：求值关系不满足对称性，因为它是有方向的。\n\n```coq\nInductive aevalR : aexp → nat → Prop :=\n  | E_ANum n :\n      aevalR (ANum n) n\n  | E_APlus (e1 e2: aexp) (n1 n2: nat) :\n      aevalR e1 n1 →\n      aevalR e2 n2 →\n      aevalR (APlus e1 e2) (n1 + n2)\n  | E_AMinus (e1 e2: aexp) (n1 n2: nat) :\n      aevalR e1 n1 →\n      aevalR e2 n2 →\n      aevalR (AMinus e1 e2) (n1 - n2)\n  | E_AMult (e1 e2: aexp) (n1 n2: nat) :\n      aevalR e1 n1 →\n      aevalR e2 n2 →\n      aevalR (AMult e1 e2) (n1 * n2).\n```\n> Noticed now we now define `inductive` in a mixed style: \n> some arg is before `:` (named), some are after `:` (anonymous).\n\nWe could do this as well\n\n```coq\n | E_APlus (e1 e2: aexp) (n1 n2: nat)\n      (H1 : aevalR e1 n1)\n      (H2 : aevalR e2 n2) :\n      aevalR (APlus e1 e2) (n1 + n2)\n```\n\n`Reserved Notation` allow us using the notation during the definition!\n\n```coq\nReserved Notation \"e '\\\\' n\" (at level 90, left associativity).\n\nInductive aevalR : aexp → nat → Prop :=\n  | E_ANum (n : nat) :\n      (ANum n) \\\\ n\n  | E_APlus (e1 e2 : aexp) (n1 n2 : nat) :\n      (e1 \\\\ n1) → \n      (e2 \\\\ n2) → \n      (APlus e1 e2) \\\\ (n1 + n2)\n  | E_AMinus (e1 e2 : aexp) (n1 n2 : nat) :\n      (e1 \\\\ n1) → \n      (e2 \\\\ n2) → \n      (AMinus e1 e2) \\\\ (n1 - n2)\n  | E_AMult (e1 e2 : aexp) (n1 n2 : nat) :\n      (e1 \\\\ n1) → \n      (e2 \\\\ n2) → \n      (AMult e1 e2) \\\\ (n1 * n2)\n\n  where \"e '\\\\' n\" := (aevalR e n) : type_scope.\n```\n\nI hated this infix `\\\\` notation...it tries to mimic `⇓` (double down arrow).\n\n                               e1 \\\\ n1\n                               e2 \\\\ n2\n                         -------------------- (E_APlus)\n                         APlus e1 e2 \\\\ n1+n2\n\nis actually:\n\n                               e1 ⇓ n1\n                               e2 ⇓ n2\n                         -------------------- (E_APlus)\n                         APlus e1 e2 ⇓ n1+n2\n\n\n> Coq Intensive:\nIf you have two variables above the line. Think about if you need `generalize dependent`. \n\n\n### Computational vs. Relational Definitions *INTERESTING* \n\nIn some cases, relational definition are much better than computational (a.k.a. functional).\n> for situations, where thing beingdefined is not easy to express as a function (or not a function at all)\n\n#### case 1 - safe division\n\n```coq\nInductive aexp : Type :=\n| ADiv (a1 a2 : aexp). (* <--- NEW *)\n```\n- functional: how to return `ADiv (ANum 5) (ANum 0)`? probably has to be `option` (Coq is total!)\n- relational: `(a1 \\\\ n1) → (a2 \\\\ n2) → (n2 > 0) → (mult n2 n3 = n1) → (ADiv a1 a2) \\\\ n3`.\n  - we can add a constraint `(n2 > 0)`.\n  \n#### case 2 - non-determinism\n\n```coq\nInductive aexp : Type :=\n| AAny (* <--- NEW *)\n```\n- functional: not a deterministic function...\n- relational: `E_Any (n : nat) : AAny \\\\ n` ... just say it's the case.\n\n\nNonetheless, functional definition is good at:\n1. by definition deterministic (need proof in relational case)\n2. take advantage of Coq's computation engine.\n3. function can be directly \"extracted\" from Gallina to OCaml/Haskell\n\nIn large Coq developments:\n1. given _both_ styles\n2. a lemma stating they coincise (等价)\n\n\n\n\n\n\nExpressions with Variables\n--------------------------\n\n### State (Environment) 环境\n\n> A _machine state_ (or just _state_) represents the current values of _all variables_ at some point in the execution of a program.\n\n```coq\nDefinition state := total_map nat.\n```\n\n\n### Syntax\n\n```coq\nInductive aexp : Type :=\n  | AId (x : string) (* <--- NEW *)\n```\n\n\n### Notations & Coercisons -- \"meta-programming\" and AST quasi-quotation\n\n#### Quasi-quotation\n\n[OCaml PPX & AST quasi-quotation](https://whitequark.org/blog/2014/04/16/a-guide-to-extension-points-in-ocaml/)\n\n> quasi-quotation enables one to introduce symbols that stand for a linguistic expression in a given instance and are used as that linguistic expression in a different instance.\n\ne.g. in above OCaml example, you wrote `%expr 2 + 2` and you get `[%expr [%e 2] + [%e 2]]`.\n\n\n#### Coq's _Notation Scope_ + Coercision == built-in Quasi-quotation\n\n```coq\n(** Coercision for constructors **)\nCoercion AId : string >-> aexp.\nCoercion ANum : nat >-> aexp.\n\n(** Coercision for functions **)\nDefinition bool_to_bexp (b : bool) : bexp := if b then BTrue else BFalse.\nCoercion bool_to_bexp : bool >-> bexp.\n\n(** Scoped Notation **)\nBind Scope imp_scope with aexp.\nBind Scope imp_scope with bexp.\n\n(** the Extension Point token **)\nDelimit Scope imp_scope with imp.\n\n(** now we can write... **)\nDefinition example_aexp := (3 + (X * 2))%imp : aexp.\nDefinition example_aexp : aexp := (3 + (X * 2))%imp. \nDefinition example_aexp := (3 + (X * 2))%imp.    (* can be inferred *)\n```\n\n\n### Evaluation w/ State (Environment)\n\nNoticed that the `st` has to be threaded all the way...\n\n```coq\nFixpoint aeval (st : state) (a : aexp) : nat :=\n  match a with\n  | AId x ⇒ st x (* <--- NEW *)              (** lookup the environment **)\n  ...\n\nFixpoint beval (st : state) (b : bexp) : bool := ...\n\nCompute (aeval (X !-> 5) (3 + (X * 2))%imp). (** ===> 13 : nat **)\n```\n\n\n\n\n\nCommands (Statement)\n--------------------\n\n```bnf\nc ::= SKIP | x ::= a | c ;; c | TEST b THEN c ELSE c FI | WHILE b DO c END\n```\n\n> we use `TEST` to avoid conflicting with the `if` and `IF` notations from the standard library.\n\n```coq\nInductive com : Type :=\n  | CSkip\n  | CAss (x : string) (a : aexp)\n  | CSeq (c1 c2 : com)\n  | CIf (b : bexp) (c1 c2 : com)\n  | CWhile (b : bexp) (c : com).\n```\n\n`notation` magics:\n\n```coq\nBind Scope imp_scope with com.\nNotation \"'SKIP'\" := CSkip : imp_scope.\nNotation \"x '::=' a\" := (CAss x a) (at level 60) : imp_scope.\nNotation \"c1 ;; c2\" := (CSeq c1 c2) (at level 80, right associativity) : imp_scope.\nNotation \"'WHILE' b 'DO' c 'END'\" := (CWhile b c) (at level 80, right associativity) : imp_scope.\nNotation \"'TEST' c1 'THEN' c2 'ELSE' c3 'FI'\" := (CIf c1 c2 c3) (at level 80, right associativity) : imp_scope.\n```\n\n\n### Unset Notations\n\n```coq\nUnset Printing Notations.  (** e1 + e2 -> APlus e1 e2 **)\nSet Printing Coercions.    (** n -> (ANum n) **)\nSet Printing All.\n```\n\n### The `Locate` command\n\n```coq\nLocate \"&&\".\n\n(** give you two, [Print \"&&\"] only give you the default one **)\nNotation\n\"x && y\" := andb x y : bool_scope (default interpretation)\n\"x && y\" := BAnd x y : imp_scope\n```\n\n\n\n\n\n\n\nEvaluating Commands\n-------------------\n\nNoticed that to _use quasi-quotation in pattern matching_, we need\n\n```coq\nOpen Scope imp_scope.\n...\n  | x ::= a1 =>     (**  CAss x a1  **)\n  | c1 ;; c2 =>     (**  CSeq c1 c1 **)\n...\nClose Scope imp_scope.\n```\n\n\nAn infinite loop (the `%imp` scope is inferred)\n\n```coq\nDefinition loop : com :=\n  WHILE true DO\n    SKIP\n  END.\n```\n\n> The fact that `WHILE` loops don't necessarily terminate makes defining an evaluation function tricky...\n\n\n### Evaluation as function (FAIL)\n\nIn OCaml/Haskell, we simply recurse, but In Coq\n\n```coq\n| WHILE b DO c END => if (beval st b)\n                      then ceval_fun st (c ;; WHILE b DO c END)\n                      else st\n(** Cannot guess decreasing argument of fix **)\n```\n\nWell, if Coq allowed (potentially) non-terminating, the logic would be inconsistent:\n\n```coq\nFixpoint loop_false (n : nat) : False := loop_false n.   (** False is proved! **)\n```\n\n#### Step-Indexed Evaluator (SUCC)\n\nChapter `ImpCEvalFun` provide some workarounds to make functional evalution works:\n1. _step-indexed evaluator_, i.e. limit the recursion depth. (think about Depth-Limited Search). \n2. return `option` to tell if it's a normal or abnormal termination.\n3. use `LETOPT...IN...` to reduce the \"optional unwrapping\" (basicaly Monadic binding `>>=`!)\n  - this approach of `let-binding` became so popular in ML family.\n\n\n### Evaluation as Relation (SUCC)\n\nAgain, we are using some fancy notation `st=[c]=>st'` to mimic `⇓`:\nIn both PLT and TaPL, we are almost exclusively use Small-Step, but in PLC, Big-Step were used:\n\n                          beval st b1 = true\n                           st =[ c1 ]=> st'\n                ---------------------------------------  (E_IfTrue)\n                st =[ TEST b1 THEN c1 ELSE c2 FI ]=> st'\n\nis really:\n\n                            H; b1 ⇓ true\n                            H; c1 ⇓ H'\n                  ----------------------------------  (E_IfTrue)\n                  H; TEST b1 THEN c1 ELSE c2 FI ⇓ H'\n\n```coq\nReserved Notation \"st '=[' c ']⇒' st'\" (at level 40).\nInductive ceval : com → state → state → Prop :=\n...\n| E_Seq : ∀c1 c2 st st' st'',\n    st =[ c1 ]⇒ st' →\n    st' =[ c2 ]⇒ st'' →\n    st =[ c1 ;; c2 ]⇒ st''\n| E_IfTrue : ∀st st' b c1 c2,\n      beval st b = true →\n      st =[ c1 ]⇒ st' →\n      st =[ TEST b THEN c1 ELSE c2 FI ]⇒ st'\n...\n  where \"st =[ c ]⇒ st'\" := (ceval c st st').\n```\n\nBy definition evaluation as relation (_in `Type` level_), \nwe need to construct _proofs_ (_terms_) to define example.\n\n...noticed that in the definition of relaiton `ceval`, we actually use the computational `aevel`, `beval`..\n...noticed that we are using explicit `∀` style rather than constructor argument style (for IDK reason). They are the same!\n\n\n\n### Determinism of Evaluation\n\n> Changing from a computational to a relational definition of evaluation is a good move because it frees us from the artificial requirement that evaluation should be a total function\n> 求值不再必须是全函数\n\n> But it also raises a question: Is the second definition of evaluation really a partial function? \n> 这个定义真的是偏函数吗？（这里的重点在于 偏函数 要求 right-unique 即 deterministic）\n\nwe can prove: \n\n```coq\nTheorem ceval_deterministic: ∀c st st1 st2,\n     st =[ c ]⇒ st1 →\n     st =[ c ]⇒ st2 →\n     st1 = st2.\nProof. ...\n```\n\n\n\n\n\n\n\n\nReasoning About Imp Programs\n----------------------------\n\n### Case `plus2_spec`\n\n```coq\nTheorem plus2_spec : ∀st n st',\n  st X = n →\n  st =[ plus2 ]⇒ st' →\n  st' X = n + 2.\nProof.\n  intros st n st' HX Heval.\n```\n\nthis looks much better as inference rules: \n\n        H(x) = n\n        H; x := x + 2 ⇓ H'\n      --------------------- (plus2_spec)\n        H'(x) = n + 2\n\nBy `inversion` on the Big Step eval relation, we can _expand_ one step of `ceval`\n(对 derivation tree 的 expanding 过程其实就是展开我们所需的计算步骤的过程)\n\n      st : string -> nat\n      =================================\n      (X !-> st X + 2; st) X = st X + 2\n\nIn inference rule:\n\n      H : string → nat\n      ================================\n      (x ↦ H(x) + 2); H)(x) = H(x) + 2\n\n\n### Case `no_whiles_terminating`\n\n\n```coq\nTheorem no_whilesR_terminating_fail:\n   forall c, no_whilesR c -> forall st, exists st', st =[ c ]=> st'.\nProof.\n  intros.\n  induction H; simpl in *. \n  - admit.\n  - admit.\n  - (* E_Seq *)\n```\n\nIf we `intros st` before `induction c`, \nthe IH would be _for particular `st`_ and too specific for `E_Seq` \n(It's actually okay for `TEST` since both branch derive from the same `st`)\n\n```coq\nIHno_whilesR1 : exists st' : state, st =[ c1 ]=> st'\nIHno_whilesR2 : exists st' : state, st =[ c2 ]=> st'\n============================\nexists st' : state, st =[ c1;; c2 ]=> st'\n```\n\nSo we'd love to\n\n```coq\ngeneralize dependent st.\ninduction H...\n- specialize (IHno_whilesR1 st).  destruct IHno_whilesR1 as [st' Hc1].\n  specialize (IHno_whilesR2 st'). destruct IHno_whilesR2 as [st'' Hc2].  (* specialize [IH2] with the existential of [IH1] **)\n  exists st''.\n  apply E_Seq with (st'); assumption.\n```\n\n\n\n\n\n\nAdditional Exerciese\n--------------------\n\n### Stack Compiler\n\n> Things that evaluate arithmetic expressions using stack:\n- Old HP Calculators\n- Forth, Postscript\n- Java Virtual Machine \n\n\n```\ninfix:\n      (2*3)+(3*(4-2))\n\npostfix:\n      2 3 * 3 4 2 - * +\n\nstack:\n      [ ]           |    2 3 * 3 4 2 - * +\n      [2]           |    3 * 3 4 2 - * +\n      [3, 2]        |    * 3 4 2 - * +\n      [6]           |    3 4 2 - * +\n      [3, 6]        |    4 2 - * +\n      [4, 3, 6]     |    2 - * +\n      [2, 4, 3, 6]  |    - * +\n      [2, 3, 6]     |    * +\n      [6, 6]        |    +\n      [12]          |\n```\n\n> Goal: compiler translates `aexp` into stack machine instructions.\n\n```coq\nInductive sinstr : Type :=\n| SPush (n : nat)\n| SLoad (x : string)   (* load from store (heap) *)\n| SPlus\n| SMinus\n| SMult.\n```\n\n### Correct Proof\n\n```coq\nTheorem s_compile_correct : forall (st : state) (e : aexp),\n  s_execute st [] (s_compile e) = [ aeval st e ].\n```\n\nTo prove this, we need a _stronger_ induction hypothesis (i.e. more general), so we state:\n\n```coq\nTheorem s_execute_theorem : forall (st : state) (e : aexp) (stack : list nat) (prog : list sinstr),\n  s_execute st stack (s_compile e ++ prog) = s_execute st ((aeval st e) :: stack) prog.\n```\n\nand go through!\n\n\n\n\n### IMP `Break/Continue`\n\n```coq\nInductive result : Type :=\n  | SContinue\n  | SBreak.\n```\n\nThe idea is that we can add a _signal_ to notify the loop!\n\nFun to go through!\n\n\n\n\n\n\n## Slide Q & A \n\n`st =[c1;;c2] => st'`\n\n- there would be intermediate thing after inversion so... we need _determinism_ to prove this!\n  - (It won't be even true in undetermincy)\n\n- the `WHILE` one (would diverge)\n  - true...how to prove?\n  - induction on derivation...!\n    - show contradiction for all cases\n- to prove `¬(∃st', ...)`, we intro the existentials and prove the `False`.\n\n\n\n### `Auto`\n\n`auto` includes `try`\n\n1. `Proof with auto.`\n2. `Set Intro Auto`\n"
  },
  {
    "path": "_posts/read_sf_lf/2019-01-13-sf-lf-13-imp-parser.md",
    "content": "---\ntitle: \"「SF-LC」13 ImpParser\"\nsubtitle: \"Logical Foundations - Lexing And Parsing In Coq\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - LF (逻辑基础)\n  - SF (软件基础)\n  - Coq\n  - 笔记\n---\n\n> the parser relies on some \"monadic\" programming idioms\n\nbasically, _parser combinator_ (But 非常麻烦 in Coq)\n\n\nLex\n---\n\n```coq\nInductive chartype := white | alpha | digit | other.\n\nDefinition classifyChar (c : ascii) : chartype :=\n  if      isWhite c then white\n  else if isAlpha c then alpha\n  else if isDigit c then digit\n  else                   other.\n  \n\nDefinition token := string.\n```\n\n\n\n\nSyntax\n------\n\n带 error msg 的 `option`:\n\n```coq\nInductive optionE (X:Type) : Type :=\n  | SomeE (x : X)\n  | NoneE (s : string).       (** w/ error msg **)\n\nArguments SomeE {X}.\nArguments NoneE {X}.\n```\n\n\nMonadic: \n\n```coq\nNotation \"' p <- e1 ;; e2\"\n   := (match e1 with\n       | SomeE p ⇒ e2\n       | NoneE err ⇒ NoneE err\n       end)\n   (right associativity, p pattern, at level 60, e1 at next level).\n\nNotation \"'TRY' ' p <- e1 ;; e2 'OR' e3\"\n   := (match e1 with\n       | SomeE p ⇒ e2\n       | NoneE _ ⇒ e3\n       end)\n   (right associativity, p pattern,\n    at level 60, e1 at next level, e2 at next level).\n```\n\n\n```coq\nDefinition parser (T : Type) :=\n  list token → optionE (T * list token).\n```\n\n```haskell\nnewtype Parser a = Parser (String -> [(a,String)])\n\ninstance Monad Parser where\n   -- (>>=) :: Parser a -> (a -> Parser b) -> Parser b\n   p >>= f = P (\\inp -> case parse p inp of\n                           []        -> []\n                           [(v,out)] -> parse (f v) out)\n```\n\n\n### combinator `many` \n\nCoq vs. Haskell \n1. explicit recursion depth, .e. _step-indexed_\n2. explicit exception `optionE`  (in Haskell, it's hidden behind the `Parser` Monad as `[]`)\n3. explicit string state `xs`    (in Haskell, it's hidden behind the `Parser` Monad as `String -> String`)\n4. explicit `acc`epted token     (in Haskell, it's hidden behind the `Parser` Monad as `a`, argument)\n\n```coq\nFixpoint many_helper {T} (p : parser T) acc steps xs :=\n  match steps, p xs with\n  | 0, _ ⇒\n      NoneE \"Too many recursive calls\"\n  | _, NoneE _ ⇒\n      SomeE ((rev acc), xs)\n  | S steps', SomeE (t, xs') ⇒\n      many_helper p (t :: acc) steps' xs'\n  end.\n\nFixpoint many {T} (p : parser T) (steps : nat) : parser (list T) :=\n  many_helper p [] steps.\n```\n\n```haskell\nmanyL :: Parser a -> Parser [a]\nmanyL p = many1L p <++ return []   -- left biased OR\n\nmany1L :: Parser a -> Parser [a]\nmany1L p = (:) <$> p <*> manyL p\n-- or\nmany1L p = do x <- p\n              xs <- manyL p\n              return (x : xs)\n```\n\n\n### `ident`\n\n\n```coq\nDefinition parseIdentifier (xs : list token) : optionE (string * list token) :=\n  match xs with\n  | [] ⇒ NoneE \"Expected identifier\"\n  | x::xs' ⇒ if forallb isLowerAlpha (list_of_string x)\n             then SomeE (x, xs')\n             else NoneE (\"Illegal identifier:'\" ++ x ++ \"'\")\n  end.\n```\n\n```haskell\nident :: Parser String\nident = do x  <- lower\n           xs <- many alphanum\n           return (x:xs)\n```\n"
  },
  {
    "path": "_posts/read_sf_lf/2019-01-14-sf-lf-14-imp-ceval.md",
    "content": "---\ntitle: \"「SF-LC」14 ImpCEvalFun\"\nsubtitle: \"Logical Foundations - An Evaluation Function For Imp\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - LF (逻辑基础)\n  - SF (软件基础)\n  - Coq\n  - 笔记\n---\n\n\nStep-Indexed Evaluator\n----------------------\n\n...Copied from `12-imp.md`:\n\n> Chapter `ImpCEvalFun` provide some workarounds to make functional evalution works:\n> 1. _step-indexed evaluator_, i.e. limit the recursion depth. (think about _Depth-Limited Search_). \n> 2. return `option` to tell if it's a normal or abnormal termination.\n> 3. use `LETOPT...IN...` to reduce the \"optional unwrapping\" (basicaly Monadic binding `>>=`!)\n>    this approach of `let-binding` became so popular in ML family.\n\n\n```coq\nNotation \"'LETOPT' x <== e1 'IN' e2\"\n   := (match e1 with\n         | Some x ⇒ e2\n         | None ⇒ None\n       end)\n   (right associativity, at level 60).\n\nOpen Scope imp_scope.\nFixpoint ceval_step (st : state) (c : com) (i : nat)\n                    : option state :=\n  match i with\n  | O ⇒ None       (* depth-limit hit! *)\n  | S i' ⇒\n    match c with\n      | SKIP ⇒\n          Some st\n      | l ::= a1 ⇒\n          Some (l !-> aeval st a1 ; st)\n      | c1 ;; c2 ⇒\n          LETOPT st' <== ceval_step st c1 i' IN    (* option bind *)\n          ceval_step st' c2 i'\n      | TEST b THEN c1 ELSE c2 FI ⇒\n          if (beval st b)\n            then ceval_step st c1 i'\n            else ceval_step st c2 i'\n      | WHILE b1 DO c1 END ⇒\n          if (beval st b1)\n          then LETOPT st' <== ceval_step st c1 i' IN\n               ceval_step st' c i'\n          else Some st\n    end\n  end.\nClose Scope imp_scope.\n```\n\n\n\nRelational vs. Step-Indexed Evaluation\n--------------------------------------\n\nProve `ceval_step` is equiv to `ceval`\n\n\n### ->\n\n```coq\nTheorem ceval_step__ceval: forall c st st',\n      (exists i, ceval_step st c i = Some st') ->\n      st =[ c ]=> st'.\n```\n\nThe critical part of proof:\n\n- `destruct` for the `i`.\n- `induction i`, generalize on all `st st' c`. \n  1. `i = 0` case contradiction\n  2. `i = S i'` case;\n     `destruct c`. \n      - `destruct (ceval_step ...)` for the `option`\n        1. `None` case contradiction\n        2. `Some` case, use induction hypothesis...\n    \n\n### <-\n\n```coq\nTheorem ceval__ceval_step: forall c st st',\n      st =[ c ]=> st' ->\n      exists i, ceval_step st c i = Some st'.\nProof.\n  intros c st st' Hce.\n  induction Hce.\n```\n\n\n\n"
  },
  {
    "path": "_posts/read_sf_lf/2019-01-15-sf-lf-15-extraction.md",
    "content": "---\ntitle: \"「SF-LC」15 Extraction\"\nsubtitle: \"Logical Foundations - Extracting ML From Coq\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - LF (逻辑基础)\n  - SF (软件基础)\n  - Coq\n  - 笔记\n---\n\n\nBasic Extraction\n----------------\n\n- OCaml   (most mature)\n- Haskell (mostly works)\n- Scheme  (a bit out of date)\n\n```coq\nExtraction \"imp1.ml\" ceval_step.\n```\n\nWhen Coq processes this command:\n\n```\nThe file imp1.ml has been created by extraction.\nThe file imp1.mli has been created by extraction.\n```\n\n\n\nControlling Extraction of Specific Types\n----------------------------------------\n\n如果不做任何处理的话...生成的 `ml` 里的 `nat` 则都会是 Church Numeral...\n\n> We can tell Coq how to extract certain `Inductive` definitions to specific OCaml types.\n> we must say:\n> 1. how the Coq type itself should be represented in OCaml\n> 2. how each constructor should be translated\n\n```coq\nExtract Inductive bool ⇒ \"bool\" [ \"true\" \"false\" ].\n```\n\n> also, for non-enumeration types (where the constructors take arguments), \n> we give an OCaml expression that can be used as a _\"recursor\"_ over elements of the type. (Think Church numerals.)\n\n```coq\nExtract Inductive nat ⇒ \"int\"\n  [ \"0\" \"(fun x → x + 1)\" ]\n  \"(fun zero succ n →\n      if n=0 then zero () else succ (n-1))\".\n```\n\n```coq\nExtract Constant plus ⇒ \"( + )\".\nExtract Constant mult ⇒ \"( * )\".\nExtract Constant eqb ⇒ \"( = )\".\n```\n\n> 注意：保证提取结果的合理性是你的责任。\n\n```coq\nExtract Constant minus ⇒ \"( - )\".\n```\n\n比如这么做很诱人……但是我们 Coq 的定义里 `0 - 1 = 0`, OCaml 的 `int` 则会有负数...\n\n\n\n### Recursor 的理论与实现 - a \"encoding\" of case expression and sum type\n\n```coq\nFixpoint ceval_step (st : state) (c : com) (i : nat)\n                    : option state :=\n  match i with\n  | O => None\n  | S i' =>\n    match c with\n```\n```ocaml\nlet rec ceval_step st c = function\n  | O -> None\n  | S i' ->\n    (match c with\n```\n```ocaml\nlet rec ceval_step st c i =\n  (fun zero succ n -> if n=0 then zero () else succ (n-1))\n    (fun _ -> None)     (* zero *)\n    (fun i' ->          (* succ *)\n    match c with\n```\n\n注意我们是如何使用 \"recursor\" 来替代 `case`, `match`, pattern matching 得。\n\nrecall _sum type_ 在 PLT 中的语法与语义：\n\n```coq\nT ::= \n  T + T\n\ne ::=\n  case e of\n    | L(e) => e\n    | R(e) => e\n\n```\n```\n                      e → e' \n                  ------------- (work inside constructor)\n                  C(e) -> C(e')\n\n                      e → e' \n          -------------------------------   (work on the expr match against)\n          case e of ... →  case e' of ...\n\n     -----------------------------------------------  (match Left constructor, substitute)\n     case L(v) of L(x) => e1 | R(y) => e2 → e1 [v/x]\n\n     -----------------------------------------------  (match Right constructor, substitute)\n     case R(v) of L(x) => e1 | R(y) => e2 → e1 [v/x]\n```\n\n可以发现 `case` 表达式可以理解为一种特殊的 application，会将其 argument 根据某种 tag （这里为构造函数） apply 到对应的 case body 上，\n每个 case body 都是和 lambda abstraction 同构的一种 binder：\n\n     L(x) => e1     ===   λx.e1\n     R(x) => e2     ===   λx.e2 \n\n     case v e1|e2   ===   (λx.e1|e2) v      -- `e1` or `e2` depends on the _tag_ wrapped on `v`\n   \n这个角度也解释了 Haskell/SML 在申明函数时直接对参数写 pattern match 的理论合理性.\n\n根据经验几乎所有的 _binding_ 都可以被 desugar 成函数（即 lambda expression).\n难点在于我们如何 re-implement 这个 _tag_ 的 _switch_ 机制?\n\n对于 `Inductive nat` 翻译到 OCaml `int` 时，这个机制可以用 `v =? 0` 来判断，因此我们的 _recursor_ 实现为\n\n```ocaml\nfun zero succ                (* partial application  *)\n  n -> if n=0                (* 判断 tag ... *)\n       then zero ()          (* 0   case =>  (λx.e1) v *)\n       else succ (n-1)       (* S n case =>  (λx.e2) v *)\n```\n\n\n\n\n\n\n"
  },
  {
    "path": "_posts/read_sf_lf/2019-01-16-sf-lf-16-auto.md",
    "content": "---\ntitle: \"「SF-LC」16 Auto\"\nsubtitle: \"Logical Foundations - More Automation\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - LF (逻辑基础)\n  - SF (软件基础)\n  - Coq\n  - 笔记\n---\n\n- `auto` - proof search\n- `Ltac` - automated forward reasoning (hypothesis matching machinery)\n- `eauto`, `eapply` - deferred instantiation of existentials\n\n\n\n`Ltac` macro\n------------\n\n```coq\nLtac inv H := inversion H; subst; clear H.\n\n(** later in the proof... **)\ninv H5.\n```\n\n\n\nThe `auto` Tactic\n-----------------\n\n> `auto` can free us by _searching_ for a sequence of applications that will prove the goal:\n\n```coq\nintros P Q R H1 H2 H3.\napply H2. apply H1. assumption.\n\n\n(** can be replaced by... **)\nauto.\n```\n\n`auto` solves goals that are solvable by _any combination_ of \n- `intros`\n- `apply` (of hypotheses from the _local_ context, by default)\n\n\n> 使用 auto 一定是“安全”的，它不会失败，也不会改变当前证明的状态： auto 要么完全解决它，要么什么也不做。\n\n> Proof search could, in principle, take an arbitrarily long time,\n> so there are limits to how far auto will search by default.  (i.e. `5`)\n\n```coq\nExample auto_example_3 : ∀(P Q R S T U: Prop),\n  (P → Q) →\n  (Q → R) →\n  (R → S) →\n  (S → T) →\n  (T → U) →\n  P →\n  U.\nProof.\n  (* 当 auto 无法解决此目标时，它就什么也不做 *)\n  auto.\n  (* 可选的参数用来控制它的搜索深度（默认为 5）, 6 就刚好能解决了！ *)\n  auto 6.\nQed.\n```\n\n\n### Hint Database 提示数据库\n\n> `auto` auto considers a __hint database__ of other lemmas and constructors.\n> common lemmas about _equality_ and _logical operators_ are installed by default.\n\n> just for the purposes of one application of `auto` \n> 我们可以为某次 `auto` 的调用扩展提示数据库，`auto using ...` \n\n```coq\nExample auto_example_6 : ∀n m p : nat,\n  (n ≤ p → (n ≤ m ∧ m ≤ n)) →\n  n ≤ p →\n  n = m.\nProof.\n  intros.\n  auto using le_antisym.\nQed.\n```\n\n\n### Global Hint Database 添加到全局提示数据库\n\n```coq\nHint Resolve T.          \n\nHint Constructors c.\n\nHint Unfold d.\n```\n\n\n### `Proof with auto.`\n\nUnder `Proof with t`, `t1...` == `t1; t`.\n\n\n\n\nSearching For Hypotheses\n------------------------\n\n对于很常见的一种矛盾情形：\n\n```coq\nH1: beval st b = false\nH2: beval st b = true\n```\n\n`contradiction` 并不能解决，必须 `rewrite H1 in H2; inversion H2`.\n\n1. 宏：\n\n```coq\nLtac rwinv H1 H2 := rewrite H1 in H2; inv H2.\n\n(** later in the proof... **)\nrwinv H H2.\n```\n\n2. `match goal` 调用宏\n\n```coq\nLtac find_rwinv :=\n  match goal with\n    H1: ?E = true,\n    H2: ?E = false\n    ⊢ _ ⇒ rwinv H1 H2\n  end.\n  \n(** later in the proof... **)\ninduction E1; intros st2 E2; inv E2; try find_rwinv; auto. (** 直接解决所有矛盾 case **)\n- (* E_Seq *)\n  rewrite (IHE1_1 st'0 H1) in *. auto.\n- (* E_WhileTrue *)\n  + (* b 求值为 true *)\n    rewrite (IHE1_1 st'0 H3) in *. auto. Qed.\n```\n\n可以看到最后只剩这种改写形式...我们也把他们自动化了：\n\n```coq\nLtac find_eqn :=\n  match goal with\n    H1: ∀x, ?P x → ?L = ?R,\n    H2: ?P ?X\n    ⊢ _ ⇒ rewrite (H1 X H2) in *\n  end.\n```\n\n配合上 `repeat`...我们可以 keep doing useful rewrites until only trivial ones are left.\n最终效果：\n\n```coq\nTheorem ceval_deterministic''''': ∀c st st1 st2,\n    st =[ c ]⇒ st1 →\n    st =[ c ]⇒ st2 →\n    st1 = st2.\nProof.\n  intros c st st1 st2 E1 E2.\n  generalize dependent st2;\n  induction E1; intros st2 E2; inv E2; \n    try find_rwinv;\n    repeat find_eqn; auto.\nQed.\n```\n\n即使我们给 IMP 加上一个 `CRepeat`（其实就是 `DO c WHILE b`)，\n会发现颠倒一下自动化的顺序就能 work 了\n\n```coq\n  induction E1; intros st2 E2; inv E2; \n    repeat find_eqn; \n    try find_rwinv; auto.\n```\n\n当然，这种「超级自动化」(hyper-automation) 并不总是现实，也不好调试...\n\n\n\n### The `eapply` and `eauto` variants\n\n> 推迟量词的实例化\n\n比如对于\n\n```coq\nExample ceval_example1:\n  empty_st =[\n    X ::= 2;;\n    TEST X ≤ 1\n      THEN Y ::= 3\n      ELSE Z ::= 4\n    FI\n  ]⇒ (Z !-> 4 ; X !-> 2).\nProof.\n  (* 我们补充了中间状态 st'... *)\n  apply E_Seq with (X !-> 2).\n  - apply E_Ass. reflexivity.\n  - apply E_IfFalse. reflexivity. apply E_Ass. reflexivity.\nQed.\n```\n\n没有 `with` 就会 `Error: Unable to find an instance for the variable st'`\n\n但其实 `st'` 的取值在后面的步骤是很明显（很好 infer/unify）的，所以 `eapply` works.\n\n\n\n\n"
  },
  {
    "path": "_posts/read_sf_plf/2019-03-01-sf-plf-01-equiv.md",
    "content": "---\ntitle: \"「SF-PLF」1 Equiv\"\nsubtitle: \"Programming Language Foundations - Program Equivalence (程序的等价关系)\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - SF (软件基础)\n  - PLF (编程语言基础)\n  - Coq\n  - 笔记\n---\n\n### issues on `coqc` module linking\n\nSome module (e.g.`Map`) not found\neither maunally `make map.vo` or proof general can solve that.\n\n\n\nBehavioral Equivalence 行为等价\n---------------------------\n\n> How to define _the correctness of program transformation_, e.g. `optimize_0plus` ?\n- in the setting w/o var (imp w/o var and state) : yield a program the evals to same number as original.\n- in the setting w/ var (full imp w/ assignment) : we need to consider the role of var and state.\n\n### Definitions\n\n> Two `aexps` or `bexps` are _behaviorally equivalent_ if they evaluate to the same result __in every state__.\n\n```coq\nDefinition aequiv (a1 a2 : aexp) : Prop :=\n  ∀(st : state), aeval st a1 = aeval st a2.\nDefinition bequiv (b1 b2 : bexp) : Prop :=\n  ∀(st : state), beval st b1 = beval st b2.\n```\n\n> For commands, We can't simply say ... if they evaluate to the same ending state\n> __some commands don't terminate in any final state at all!__\n\nSo to define, they either or...\n1. both diverge 都发散\n2. both terminate in the same final state 都在同一个状态停机\n\nA compact way is \n> \"if the first one terminates in a particular state then so does the second, and vice versa.\"\n\n```coq\nDefinition cequiv (c1 c2 : com) : Prop :=\n  ∀(st st' : state),\n    (st =[ c1 ]⇒ st') ↔ (st =[ c2 ]⇒ st'). \n```\n\n### Example 1 - Simple (but demonstrated) \n\n```coq\nTheorem skip_left : forall c,\n  cequiv (SKIP;; c) c.\nProof.\n  intros c st st'. split; intros H.\n  - (* -> *)         \n    inversion H; subst.  (* inverse E_Seq   *)\n    inversion H2. subst. (* inverse E_Skip  *)\n    assumption.\n  - (* <- *)             (* reversely *)\n    apply E_Seq with st. (* apply E_Seq *)\n    apply E_Skip.        (* apply E_Skip *)\n    assumption.\nQed.\n```\n\nNoticed that the `inversion` is like use the _inverse function_ of constructors.\n\n\n### Example 2 - WHILE true non-terminating\n\none interesting theorem is that we can prove `WHILE <things ⇓ true>` is not terminating.\nand is equivalent to _any other non-terminating program_, e.g. `WHILE BTrue DO SKIP END`: (因为我们的「等价」只要求同「发散」即可)\n\n```coq\nTheorem WHILE_true : ∀b c,\n  bequiv b true →\n  cequiv\n    (WHILE b DO c END)\n    (WHILE true DO SKIP END).\n```\n\n\n### Example 3 - Loop Unrolling\n\n> _any number of copies of the body_ can be \"unrolled\" without changing meaning\n\n```coq\nTheorem loop_unrolling : ∀b c,\n  cequiv\n    (WHILE b DO c END)         \n    (TEST b THEN (c ;; WHILE b DO c END) ELSE SKIP FI).    (** 展开一层 **)\n```\n\n\n### Example 4 - Use of extenionality 外延性\n\n`x !-> m x ; x` is same map with `m` by extenionality!\n\n```coq\nTheorem identity_assignment : ∀x,\n  cequiv (x ::= x) SKIP.\n```\n\n\n\n\n\nProperties of Behavioral Equivalence 行为等价的性质\n--------------------------------------------\n\n\n### 等价关系 (Equivalence)\n\n> 自反性（reflexive）、对称性（symmetric）和传递性 （transitive）\n\n\n### 同余关系（Congruence）\n\n> That is, the equivalence of two subprograms implies the equivalence of the larger programs in which they are _embedded_\n> 如果两个子程序等价，那么当二者所在的更大的程序中_只有二者不同_时， 这两个更大的程序也等价\n\n\n              aequiv a1 a1'\n      -----------------------------\n      cequiv (x ::= a1) (x ::= a1')\n\n              cequiv c1 c1'\n              cequiv c2 c2'\n         --------------------------\n         cequiv (c1;;c2) (c1';;c2')\n\n\n> 这个术语应该是来自抽象代数 : 能在运算下保持的等价关系\n> ...in the sense that algebraic operations done with equivalent elements will yield equivalent elements.\n> [Congruence relation](https://en.wikipedia.org/wiki/Congruence_relation)\n\n```coq\nTheorem CAss_congruence : ∀x a1 a1',     (** cequiv 是集合 commands 上的等价关系 **)\n  aequiv a1 a1' →\n  cequiv (CAss x a1) (CAss x a1').       (** 在 `CAss` 这个 operation 下保持等价 => 同余 **)\n  cequiv (x ::= a1) (x ::= a1').         (** 或，在 `::=` 这个 command 下保持等价 => 同余 **)\n```\n\n> 在 commands 上等价但不同余的关系?\n\nI guess...\"both terminating\" relation?\nwhich is equivalence relation on commands, but the equivalence would not be maintained after, say `C_WHILE` operation.\n\n\n\n### Example - Using Congruence\n\n```coq\nExample congruence_example:\n  cequiv\n    (* 程序 1： *)\n    (X ::= 0;;\n     TEST X = 0\n     THEN\n       Y ::= 0\n     ELSE\n       Y ::= 42\n     FI)\n    (* 程序 2： *)\n    (X ::= 0;;\n     TEST X = 0\n     THEN\n       Y ::= X - X (* <--- 这里不同 *)\n     ELSE\n       Y ::= 42\n     FI).\nProof.\n  apply CSeq_congruence.\n  - apply refl_cequiv.\n  - apply CIf_congruence.\n    + apply refl_bequiv.\n    + apply CAss_congruence.     (** <--- 化简到只需要证明 aequiv 0 (X - X) **) \n      unfold aequiv. simpl.\n      * symmetry. apply minus_diag.\n    + apply refl_cequiv.\nQed.\n```\n\n\n\n\n\nProgram Transformations 程序变换\n------------------------------\n\n> A program transformation is _sound_ if it preserves the behavior of the original program.\n> 如果一个程序变换保留了其原始行为，那么它就是_可靠_的\n\n我们可以定义在不同集合 `aexp, bexp, com` 上的 sound 关系：\n(有趣的是，`Inductive` 定义的非 `Prop` 的 `Type`, 确实就是 `Set`, 这是一种 PL 和数学的 Correspondence)\n- 当我们的 datatype 是 constructor 时 => 不交并\n- 当我们的 datatype 有 recursive   时 => 集合的递归定义\n\n\n```coq\nDefinition atrans_sound (atrans : aexp → aexp) : Prop :=\n  ∀(a : aexp), aequiv a (atrans a).\nDefinition btrans_sound (btrans : bexp → bexp) : Prop :=\n  ∀(b : bexp), bequiv b (btrans b).\nDefinition ctrans_sound (ctrans : com → com) : Prop :=\n  ∀(c : com), cequiv c (ctrans c).\n```\n\n\n### Constant Folding 常量折叠\n\n> An expression is _constant_ when it contains no variable references.\n> 不引用变量的表达式为_常量_\n\n> Constant folding is an _optimization_ that finds constant expressions and replaces them by their values.\n> 常量折叠是一种找到常量表达式并把它们替换为其值的优化方法。\n\n\n### Soundness of Constant Folding\n\n#### `aexp`\n\n```coq\nTheorem fold_constants_aexp_sound :\n  atrans_sound fold_constants_aexp.\nProof.\n  unfold atrans_sound. intros a. unfold aequiv. intros st.\n  \n(** 这个时候的状态：**)\n\n  a : aexp\n  st : state\n  ============================\n  aeval st a = aeval st (fold_constants_aexp a)\n  \n```\n\n#### `bexp`\n\n证明 `btrans_sound fold_constants_bexp.` 要难一些，因为其中还用到了 `fold_constants_aexp`, 所以我们需要一些技巧\n\n```coq\n(** 如果不记住而是直接 destruct 的话，这部分信息就丢失了 **)\n    remember (fold_constants_aexp a1) as a1' eqn:Heqa1'.\n    remember (fold_constants_aexp a2) as a2' eqn:Heqa2'.\n\n(** 保留了这部分信息的目的是，使用 aexp 的可靠性定理来建立 aexp 与 值 的关系 **)\n    replace (aeval st a1) with (aeval st a1') by\n       (subst a1'; rewrite <- fold_constants_aexp_sound; reflexivity).\n    replace (aeval st a2) with (aeval st a2') by\n       (subst a2'; rewrite <- fold_constants_aexp_sound; reflexivity).\n\n(** 最后才分类讨论 **)\n    destruct a1'; destruct a2'; try reflexivity.\n```\n\n#### `cmd`\n\n主要技巧在于配合使用 `Congruence` 与 `IH` 解决大部分 case，然后分类讨论 `fold_constants_bexp` 用 `sound` 做替换解决剩余 case.\n\n\n\n### Soundness of (0 + n) \n\n类似，但是接下来我们就可以证明先 ` fold_constants` 再 `optimize_0plus` 也是 sound 的.\n这里我更 general 得证明了 `ctrans` 关系的传递性：\n\n```coq\nTheorem trans_ctrans_sound : forall tr1 tr2,\n  ctrans_sound tr1 -> \n  ctrans_sound tr2 -> \n  ctrans_sound (fun c => tr2 (tr1 c)).\n```\n\n\n\n\n\n\nProving Inequivalence 证明程序不等价\n-----------------------------\n\n在这个例子中，`subst_aexp` 是 sound 得，被称为 _Constant Propagation_ (常量传播)\n\n```coq\n(**    [X := 42 + 53](Y + X)  =>  Y + (42 + 53)    **)\nExample subst_aexp_ex :\n  subst_aexp X (42 + 53) (Y + X)%imp = (Y + (42 + 53))%imp.\nProof. reflexivity. Qed.\n```\n\n所以我们断言这么做是 always sound 得： \n\n```coq\nDefinition subst_equiv_property := ∀x1 x2 a1 a2,\n  cequiv (x1 ::= a1;; x2 ::= a2)\n         (x1 ::= a1;; x2 ::= subst_aexp x1 a1 a2).\n```\n\n然而如果 `a1` 不是常量，副作用很容易让这个转换 unsound\n那么怎么证明 `¬subst_equiv_property` (即该性质不成立)? 举一个反例就好\n\n\nInformal proof\n- provide a witness \n\nFormal\n- give counterexamples via `remember`, then show `⊥`.\n\n```coq\n(** 给出一组反例，使用性质证明他们 cequiv **)\n\n  remember (X ::= X + 1;;\n            Y ::= X)%imp     as c1.\n  remember (X ::= X + 1;;\n            Y ::= X + 1)%imp as c2.\n  assert (cequiv c1 c2) by (subst; apply Contra).\n  \n(* => *)  Heqc1 : c1 = (X ::= X + 1;; Y ::= X)%imp\n          Heqc2 : c2 = (X ::= X + 1;; Y ::= X + 1)%imp\n          H : cequiv c1 c2\n          ============================\n          False\n    \n    \n(** 给出他们将 eval 出不同的 heap **)\n\n  remember (Y !-> 1 ; X !-> 1) as st1.\n  remember (Y !-> 2 ; X !-> 1) as st2.\n  assert (H1 : empty_st =[ c1 ]=> st1);\n  assert (H2 : empty_st =[ c2 ]=> st2);\n\n  apply H in H1. (** 使用 H : cequiv c1 c2 , 我们得到 **)\n  \n(* => *)  H1 : empty_st =[ c2 ]=> st1\n          H2 : empty_st =[ c2 ]=> st2\n          ============================\n          False\n\n\n(** 利用 ceval 的 deterministic **)\n\n  assert (Hcontra : st1 = st2)\n    by (apply (ceval_deterministic c2 empty_st); assumption).\n    \n(* => *)  Hcontra : st1 = st2\n          ============================\n          False\n\n    \n(** st1, st2 are map, which are actually function! \n    这时我们可以反用 functional extenionality，直接 apply Y 然后 discrinminate **)\n\n  assert (Hcontra' : st1 Y = st2 Y)\n    by (rewrite Hcontra; reflexivity).\n  subst. inversion Hcontra'.  Qed.\n```\n\n\n\n\nExtended Exercise: Nondeterministic Imp \n---------------------------------------\n\n> HAVOC roughly corresponds to an _uninitialized variable_ in a low-level language like C. \n> After the HAVOC, the variable holds a fixed but arbitrary number.\n\n我们增加一个 `HAVOC X` 语句（大灾难），会为 X 随机赋一个值...类似于「未初始化变量」\n\n\n```coq\nInductive com : Type :=\n  ...\n  | CHavoc : string → com. (* <--- 新增 *)\n\nNotation \"'HAVOC' l\" :=\n  (CHavoc l) (at level 60) : imp_scope.\n  \n\nInductive ceval : com -> state -> state -> Prop :=\n  ...\n  | E_Havoc : forall st (n : nat) x,\n      st =[ HAVOC x ]=> (x !-> n)        (** can eval to arbitraty heap **)\n```\n\n\n\n\n\n\n---\n\n\n# Small-Step\n\n\n## deterministic\n\nalso the def of partial function?\n\n`solve_by_inverts`\n\nin LTac. (used to generate proof)\nLTac doesn't have _termination check_. (might not be able to find...)\n\n`match` is back-tracking point.\n\n\nnumber passing in = depth of the iteration/recursion\n\n\n\n\n---\n\n\n`ST_Plus2` need `value v1`. not redundant with `ST_Plus1`\nwe might have things not `value` but cannot take step as well.\n\n\n---\n\nStrong Progress\n\n\nNormal form\n\n= no relation related to (so cannot step to)\n\n\nvs Value...\n\n\n`destruct (apply t)`. \ncan we do that?\n\n\n---\n\nSlide Q&A.\n\n\n\nvalue_not_sae_as normal_form\n\ne.g (1+2) + 7\ne.g. 3 + 7\n\n\n---\n\nOne-step\n\n* plus\n* left  + 0\n* right + 0\n\nInf-step -> Inf terms\n\ngo from 3 to `3+0`\n\n\n---\n\n\nStuck\n\n\nNo StepR\n\n0 term can step into\n\n\n---\n\n\nMulti-Step Reduction `->*`\n\n```coq\nInductive multi {X : Type} (R : relation X) : relation X :=\n  | multi_refl : ∀(x : X), multi R x x\n  | multi_step : ∀(x y z : X),\n                    R x y →\n                    multi R y z →\n                    multi R x z.\n```\n\n\ncan be either defined as a \"head + tail\" style (or \"tail + head\" style), or \"refl + trans\" style (as in `Rel.v`).\n\nthe `trans` relation are underteministic  in terms of the transtive relation using. (you can throw infinitely many `trans` constructors in)\n\n> having multiple form so we can jump back and forth to pick one easier for proof.\n\n\n--- \n\nPLT PM lang\n\nmultiple smallstep relation can be markded deepner state.qjw\ner state.\n\n---\n\nIMP\n\n\n`astep` no need for `value``\n\nfor `If`, in PLT we have 2 rules for T/F.\nhere we can compute...\n\n\n---\n\n\n\nPar w/o Concurrency is deterministic\n(not vice versa)\n\nsuddenly `/ ->* /` tuple\n\n\n\n"
  },
  {
    "path": "_posts/read_sf_plf/2019-03-02-sf-plf-02-hoare-1.md",
    "content": "---\ntitle: \"「SF-PLF」2 Hoare\"\nsubtitle: \"Programming Language Foundations - Hoare Logic, Part I \"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - SF (软件基础)\n  - PLF (编程语言基础)\n  - Coq\n  - 笔记\n---\n\nTBD\n"
  },
  {
    "path": "_posts/read_sf_plf/2019-03-03-sf-plf-03-hoare-2.md",
    "content": "---\ntitle: \"「SF-PLF」3 Hoare2\"\nsubtitle: \"Programming Language Foundations - Hoare Logic, Part II\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - SF (软件基础)\n  - PLF (编程语言基础)\n  - Coq\n  - 笔记\n---\n\nTBD\n\n"
  },
  {
    "path": "_posts/read_sf_plf/2019-03-04-sf-plf-04-hoare-logic.md",
    "content": "---\ntitle: \"「SF-PLF」4 HoareAsLogic\"\nsubtitle: \"Programming Language Foundations - Hoare Logic as a Logic\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - SF (软件基础)\n  - PLF (编程语言基础)\n  - Coq\n  - 笔记\n---\n\nTBD\n"
  },
  {
    "path": "_posts/read_sf_plf/2019-03-05-sf-plf-05-smallstep.md",
    "content": "---\ntitle: \"「SF-PLF」5 Smallstep\"\nsubtitle: \"Programming Language Foundations - Small-Step Operational Semantics\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - SF (软件基础)\n  - PLF (编程语言基础)\n  - Coq\n  - 笔记\n---\n\n\nRecall Big-step Pros & Cons\n---------------------------\n\n## Big-step\n\n> 一步到位 :  _eval to its final value (plus final store)_ \n\n### Pros - natural (so called _natural semantics_), \"all in one big step\"\n\n### Cons - not catch the _essence of how program behave_ \n\n> 大步语义只是一个 `程序 ↦ 结果` 这样的 pair 集合，而「如何一步步处理」才是程序「执行」的本质\n\nnot just input state get mapped to output state.\nbut also _intermediate state_ (which could be observed by _concurrent_ code!)\n\n\n### Cons - not technically expressive enough to express _exception / crash / non-termination_ \n\n> 比如说，大步语义无法区分「不停机」与「卡住」\n> two quite different reasons of \"fail to map a given state to any ending state\"\n\n1. 不停机 nontermination - we want to allow this (infinite loop is the price paid for usability)\n2. 卡住 getting stuck / undefiend behaviour 未定义行为  - we want to prevent (wrong)\n\n- `WHILE_true_nonterm` 仅仅表达了「程序不能再 take step」，无法与「卡住」区分\n- `WHILE_true` 更是直接让任何「无限循环」的程序都「等价」了...而忽略了中间状态和 effect (作用)\n\n> we need _a way of presenting semantics that distinguish_ nontermination from erroneous \"stuck states\"\n\n\n## Small-step \n\n> 更精细化 :  a _finer-grained_ way of defining and reasoning about program behaviors. \n> 原子步骤 :  _\"atomic steps\"_ of computation are performed. \n\n\n\n\n\n\n\nA Toy Language\n--------------\n\nOnly Constant and Plus\n\n```coq\nInductive tm : Type :=\n  | C : nat → tm (* Constant *)\n  | P : tm → tm → tm. (* Plus *)\n```\n  \n### Big-Step\n\n`==>` is really `⇓`\n  \n                ---------        (E_Const)\n                C n ==> n\n\n                t1 ==> n1\n                t2 ==> n2\n            -------------------  (E_Plus)\n            P t1 t2 ==> n1 + n2\n  \n\n### Small-Step\n\n> single reduction step\n> find leftmost redex\n\n      -------------------------------   (ST_PlusConstConst)\n      P (C n1) (C n2) --> C (n1 + n2)\n\n              t1 --> t1'\n          --------------------          (ST_Plus1)\n          P t1 t2 --> P t1' t2\n\n              t2 --> t2'\n      ----------------------------      (ST_Plus2)\n      P (C n1) t2 --> P (C n1) t2'\n\n\n\n\n\n\nRelations\n---------\n\n> Check notes of `rel` and `tactics` for more details about bi-relation.\n\n\n### Deterministic 确定性\n\n> a.k.a Partial Function. \n> in terms of its _right uniqueness_ under mathematical context, not its emphasise on _partial_ under programming context)\n\n```coq\nDefinition deterministic {X : Type} (R : relation X) :=\n  ∀x y1 y2 : X, R x y1 → R x y2 → y1 = y2.\n```\n\n`deterministic step` can be proved by induction on derivation `x --> y1` \n- use `generalize dependent y2`!\n- in informal proof, we usually just take `∀ y2` by default.\n\n\n### `Ltac solve_by_inverts n`\n\n```coq\nLtac solve_by_inverts n :=\n  match goal with | H : ?T ⊢ _ ⇒\n  match type of T with Prop ⇒\n    solve [\n      inversion H;\n      match n with S (S (?n')) ⇒ subst; solve_by_inverts (S n') end ]\n  end end.\n```\n\n\n### Values 值\n\n#### Abstract Machine 抽象机!\n\n> think of the `-->` relation as defining an _abstract machine_:\n\n- term = _state_ of machine 项 = 机器状态\n- step = atomic unit of computation (think as assembly opcode / CPU instructrion)\n- _halting state_ = no more computation. 停机状态\n\n> execute a term `t`:\n\n- starting state = `t`\n- repeatedly use `-->` \n- when halt, _read out_ the _final state_ as result of execution\n\n> Intutively, we call such (final state) terms _values_.\nOkay so the point is...this language is simple enough (no stuck state).\nand in this lang, value can only be `C`onst:\n\n> 在这个语言中，我们「规定」只有 `C`onst 是「值」:\n\n```coq\nInductive value : tm → Prop :=\n  | v_const : ∀n, value (C n).\n```\n\n> and we can write `ST_Plus2` more elegant:\nwell...in this lang, not really, since only one form of value to write out.\nin cases we have multiple form of value, by doing this we don't have to write out any cases.\n\n             value v1\n            t2 --> t2'\n        --------------------  (ST_Plus2)\n        P v1 t2 --> P v1 t2'\n\n\n\n### Strong Progress and Normal Forms 强可进性和正规式\n\n\n> _strong progress_: every term either is a value or can \"make progress\"\n\n```coq\nTheorem strong_progress : ∀t,\n  value t ∨ (∃t', t --> t').\n```\n\n\n> terms that cannot make progress.\n> for an arbitrary relation `R` over an arbitrary set `X`\n\n\n> _normal form_: term that cannot make progress (take a step)\n> 其实我个人比较喜欢理解为「常态」或「无能量稳定态」\n\n```coq\nDefinition normal_form {X : Type} (R : relation X) (t : X) : Prop :=\n  ¬∃t', R t t'.\n```\n\n\n> theorem: _in this language_, normal forms and values are actually the same thing. \n\n```coq\nLemma value_is_nf : v, value v → normal_form step v.\nLemma nf_is_value : ∀t, normal_form step t → value t.\nCorollary nf_same_as_value : ∀t, normal_form step t ↔ value t.\n```\n\n\n#### Value != Normal Form (not always)\n\n> value       is a _syntactic_ concept : it is defined by looking at the form of a term \n> normal form is a _semantic_  one     : it is defined by looking at how the term steps.\n\n\n> E.g. we can defined term that can take a step as \"value\":\n> 添加一个不是 normal form 的 value\n\n```coq\nInductive value : tm → Prop :=\n  | v_const : ∀n, value (C n)\n  | v_funny : ∀t1 n2, value (P t1 (C n2)). (* <--- it can actually progress! *)\n```\n\n> 或者更改 `step` 让 value 不是 normal form...\n\n```coq\nInductive step : tm -> tm -> Prop :=\n  | ST_Funny : forall n,   \n      C n --> P (C n) (C 0)                (* <--- or a weird  *)\n```\n\n\n\n\n\n\n\n\nMulti-Step Reduction `-->*` 多步规约\n----------------------------------\n\n> relation `multi R`: _multi-step closure of R_ \n> same as `clos_refl_trans_1n` in `Rel` chapter.\n\n```coq\nInductive multi {X : Type} (R : relation X) : relation X :=\n  | multi_refl : ∀(x : X), multi R x x\n  | multi_step : ∀(x y z : X),\n                    R x y →\n                    multi R y z →\n                    multi R x z.\n```\n\n以上是一种方便的定义，而以下则给了我们两个 helper 定理：\n\n```coq\nTheorem multi_R : ∀(X : Type) (R : relation X) (x y : X),\n    R x y → \n    multi R x y.\n\nTheorem multi_trans : ∀(X : Type) (R : relation X) (x y z : X),\n    multi R x y →\n    multi R y z →\n    multi R x z.\n```\n\n\n### Normal Forms Again\n\n\n```coq\nDefinition step_normal_form := normal_form step.  (** 这个是一个「性质」 Property : _ -> Prop , 从 polymorphic 的 [normal_form] 以 [step] 实例化而来 **) \nDefinition normal_form_of (t t' : tm) :=          (** 是两个项之间的（i.e. 定义在 [tm] 集合上的) 二元关系, 即 t' 是 t 的正规式 **)\n  (t -->* t' ∧ step_normal_form t').\n  \nTheorem normal_forms_unique:                      (** single-step reduction is deterministic 可以推出 normal form is unique for a given term **)\n  deterministic normal_form_of.\n```\n\n\n### Normalizing 总是可正规化得  -- \"Evaluating to completion\"\n\n> something stronger is true for this language (though not for all languages)\n> reduction of _any_ term `t` will eventually reach a normal form (我们知道 STLC 也有这个特性)\n\n```coq\nDefinition normalizing {X : Type} (R : relation X) :=\n  ∀t, ∃t',\n    (multi R) t t' ∧ normal_form R t'.\n```\n\nTo prove this, we need lemma showing some _congruence_ of `-->*`: \n同余关系，不过这次是定义在 `-->*` 这个关系上，again，同余指的是「关系对于结构上的操作保持」\n\n```coq\nLemma multistep_congr_1 : ∀t1 t1' t2,\n     t1 -->* t1' → \n     P t1 t2 -->* P t1' t2.\n\nLemma multistep_congr_2 : ∀t1 t2 t2',\n     value t1 →\n     t2 -->* t2' →\n     P t1 t2 -->* P t1 t2'.\n```\n\nThen we can prove...\n\n```coq\nTheorem step_normalizing :\n  normalizing step.\n```\n\n\n\n### Equivalence of Big-Step and Small-Step\n\n```coq\nTheorem eval__multistep : ∀t n,\n  t ==> n → t -->* C n.\n\nTheorem multistep__eval : ∀t t',\n  normal_form_of t t' → ∃n, t' = C n ∧ t ==> n.    (* might be better to say value here? *)\n```\n\n\n\n\nAdditional: Combined Language\n-----------------------------\n\nWhat if we combined the lang `Arith` and lang `Boolean`?\nWould `step_deterministic` and `strong_progress` still holds?\n\nIntuition:\n- `step_deterministic` should still hold\n- but `strong_progress` would definitely not!!\n  - now we mixed two _types_ so we will have stuck terms e.g. `test 5` or `tru + 4`.\n  - we will need type check and then we would be able to prove `progress` (which require well-typeness)\n\n```coq\nTheorem strong_progress :\n  (forall t, value t \\/ (exists t', t --> t')) \\/\n  ~ (forall t, value t \\/ (exists t', t --> t')).\nProof.\n  right. intros Hcontra.\n  remember (P tru fls) as stuck.   (** 类似 disprove equiv = 举一个反例就好 **)\n  specialize (Hcontra stuck).\n  destruct Hcontra as [Hcvalue | Hcprogress]; subst.\n  - inversion Hcvalue; inversion H.\n  - destruct Hcprogress. inversion H. inversion H3. inversion H4.\nQed.\n```\n\n\n\n\n\nSmall-Step IMP \n--------------\n\n又到了老朋友 IMP……还好没练习……简单看一下\n\n首先对于定义小步语义，我们需要定义 `value` 和 `-->` (step)\n\n### `aexp`, `bexp` \n\n```coq\nInductive aval : aexp → Prop :=\n  | av_num : ∀n, aval (ANum n).\n```\n\n`bexp` 不需要 `value` 因为在这个语言里 `BTrue` 和 `BFalse` 的 step 总是 disjointed 得，所以并没有任何复用 `value` predicate 的时候\n\n\n### `-->a`, `-->b` \n\n这里，我们先为 `aexp`, `bexp` 定义了它们各自的小步语义，\n\n> 但是，其实 from PLT we know, 我们其实也可以直接复用 `aexp`, `bexp` 的大步语义！\n> 1. 大步语义要短得多\n> 2. `aexp`, `bexp` 其实并不会出\n>   - 「不停机」: 没有 jump 等控制流结构\n>   - 「异常」/「卡住」: 我们在 meta-language 的 AST 里就区分了 `aexp` 和 `bexp`，相当于主动约束了类型，所以不会出现 `5 || 3` 这样 type error 的 AST\n\n\n### `cmd`, `-->`\n\n> 我们把 `SKIP` 当作一个「命令值（command value）」 i.e. 一个已经到达 normal form 的命令。\n> - 赋值命令归约到 `SKIP` （和一个新的 state）。\n> - 顺序命令等待其左侧子命令归约到 `SKIP`，然后丢弃它，并继续对右侧子命令归约。\n\n> 对 `WHILE` 命令的归约是把 `WHILE` 命令变换为条件语句，其后紧跟同一个 `WHILE` 命令。\n\n> 这些都与 PLT 是一致的\n\n\n\n\n\n\nConcurrent IMP\n--------------\n\n为了展示 小步语义 的能力，let's enrich IMP with concurrency.\n- unpredictable scheduling (subcommands may be _interleaved_)\n- _share same memory_\n\nIt's slightly confusing here to use `Par` (meaning _in parallel_) \nI mean, concurrency _could_ be in parallel but it doesn't have to...\n\n```coq\nInductive com : Type :=\n  | CPar : com → com → com. (* <--- NEW *)\n\nInductive cstep : (com * state) → (com * state) → Prop :=\n  (* New part: *)\n  | CS_Par1 : ∀st c1 c1' c2 st',\n      c1 / st --> c1' / st' →\n      (PAR c1 WITH c2 END) / st --> (PAR c1' WITH c2 END) / st'\n  | CS_Par2 : ∀st c1 c2 c2' st',\n      c2 / st --> c2' / st' →\n      (PAR c1 WITH c2 END) / st --> (PAR c1 WITH c2' END) / st'\n  | CS_ParDone : ∀st,\n      (PAR SKIP WITH SKIP END) / st --> SKIP / st\n```\n\n\n\n\n\n\n\n\nA Small-Step Stack Machine  小步栈机\n-----------------------------------\n\n啊哈！IMP 章节 Stack Machine，我们之前仅仅定义了 `Fixpoint s_execute` 和 `Fixpoint s_compile`，这里给出其小步语义\n> 对于本身就与「小步语义」在精神上更统一的「抽象机」，我怀疑其语义都应该是足够「小」的（即大小步将是一致的？)\n\n```coq\nDefinition stack := list nat.\nDefinition prog  := list sinstr.\n\nInductive stack_step : state -> prog * stack -> prog * stack -> Prop :=\n  | SS_Push : forall st stk n p',\n    stack_step st (SPush n :: p', stk)      (p', n :: stk)\n  | SS_Load : forall st stk i p',\n    stack_step st (SLoad i :: p', stk)      (p', st i :: stk)\n  | SS_Plus : forall st stk n m p',\n    stack_step st (SPlus :: p', n::m::stk)  (p', (m+n)::stk)\n  | SS_Minus : forall st stk n m p',\n    stack_step st (SMinus :: p', n::m::stk) (p', (m-n)::stk)\n  | SS_Mult : forall st stk n m p',\n    stack_step st (SMult :: p', n::m::stk)  (p', (m*n)::stk).\n    \n(** closure of stack_step **)\nDefinition stack_multistep st := multi (stack_step st).\n```\n\n### Compiler Correctness\n\n> 「编译器的正确性」= the notion of _semantics preservation_ (in terms of observable behaviours)\n>   S  = `e`\n>   C  = `s_compile e`\n> B(S) = `aeval st e` \n> B(C) = functional `s_execute` \n>      | relational `stack_multistep` \n\n之前我们证明过 _functional/computational_ `Fixpoint` 的性质\n\n```coq\nTheorem s_compile_correct : forall (st : state) (e : aexp),\n  s_execute st [] (s_compile e) = [ aeval st e ].\n\n(** 重要的是这个更一般的「描述了 prog 如何与 stack 交互」的定理 **)\nTheorem s_execute_theorem : forall (st : state) (e : aexp) (stack : list nat) (prog : list sinstr),\n    s_execute st                  stack  (s_compile e ++ prog) \n  = s_execute st ((aeval st e) :: stack)                 prog.\n\n```\n\n现在则是证明 _relational_ `Inductive` 的性质，同样我们需要一个更一般的定理（然后原命题作为推论）\n\n```coq\nTheorem stack_step_theorem : forall (st : state) (e : aexp) (stack : list nat) (prog : list sinstr),\n  stack_multistep st\n                  ((s_compile e ++ prog),                 stack) \n                  (                prog , (aeval st e) :: stack).      (** 这里 prog 和 stack 的交互本质上和上面是一样的 **)\nProof.\n  unfold stack_multistep.\n  induction e; intros; simpl in *;        (** 证明 induction on aexp，然后利用 transivitiy、constructor 与 IH 即可，非常快 **)\n    try (apply multi_R; constructor);\n    try (\n        repeat (rewrite <- app_assoc);\n        eapply multi_trans; try apply IHe1;\n        eapply multi_trans; try apply IHe2;\n        eapply multi_R; constructor\n      ).\n      \nDefinition compiler_is_correct_statement : Prop := forall (st : state) (e : aexp),\n  stack_multistep st (s_compile e, []) ([], [aeval st e]).\n```\n\n\n\n\n\n\nAside: A `normalize` Tactic\n---------------------------\n\nEven with `eapply` and `auto`...manual normalization is tedious:\n\n```coq\nExample step_example1' :\n  (P (C 3) (P (C 3) (C 4)))\n  -->* (C 10).\nProof.\n  eapply multi_step. auto. simpl.\n  eapply multi_step. auto. simpl.\n  apply multi_refl.\nQed.\n```\n\nWe could write custom `Tactic Notation`...(i.e. tactic macros)\n\n```coq\nTactic Notation \"print_goal\" :=\n  match goal with ⊢ ?x ⇒ idtac x end.\n  \nTactic Notation \"normalize\" :=\n  repeat (print_goal; eapply multi_step ;\n            [ (eauto 10; fail) | (instantiate; simpl)]);\n  apply multi_refl.\n```\n\n`instantiate` seems here for intros `∃`?\n\n```coq\nExample step_example1''' : exists e',\n  (P (C 3) (P (C 3) (C 4)))\n  -->* e'.\nProof.\n  eapply ex_intro. normalize.\nQed.\n```\n\nBut what surprise me is that we can `eapply ex_intro`, which leave the `∃` as a hole `?ex` (unification variable).\n"
  },
  {
    "path": "_posts/read_sf_plf/2019-03-06-sf-plf-06-types.md",
    "content": "---\ntitle: \"「SF-PLF」6 Types\"\nsubtitle: \"Programming Language Foundations - Type Systems\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - SF (软件基础)\n  - PLF (编程语言基础)\n  - Coq\n  - 笔记\n---\n\nThis chapter:\n- typing relation                                             -- 定型关系\n- `type preservation` and `progress` (i.e. soundness proof)   -- 类型保留，可进性\n\n\nTyped Arithmetic Expressions (Toy Typed Language)\n-------------------------------------------------\n\nThe toy lang from `SmallStep` is too \"safe\" to demonstrate any __runtime (or dynamic) type errors__.  -- 运行时类型错误\nSo that's add some operations (common church numeral ones), and `bool` type.\n\n...same teaching order as TAPL. In PLT, we went directly to STLC.\n\n### Syntax\n\n```coq\nt ::= tru | fls | test t then t else t | zro | scc t | prd t | iszro t\n```\n\n```coq\nInductive tm : Type :=\n  | tru : tm\n  | fls : tm\n  | test : tm → tm → tm → tm\n  | zro : tm\n  | scc : tm → tm\n  | prd : tm → tm\n  | iszro : tm → tm.\n\n(* object language has its own bool / nat , 这里不使用 Coq (meta-language) 比较 pure 一些? *)\nInductive bvalue : tm → Prop :=\n  | bv_tru : bvalue tru\n  | bv_fls : bvalue fls.\nInductive nvalue : tm → Prop :=\n  | nv_zro : nvalue zro\n  | nv_scc : ∀t, nvalue t → nvalue (scc t).  (** 注意这里 nv_scc 是描述所有 [scc t] 是 nvalue 的一个 constructor / tag **)\n\n(* [value?] predicate *)\nDefinition value (t : tm) := bvalue t ∨ nvalue t.\n```\n\n\n### Automation \n\n`Hint` are added to database to help with `auto`.\nMore details on `auto. eapply. eauto.` were mentioned in `lf/Auto`.\n\n```coq\nHint Constructors bvalue nvalue.\nHint Unfold value.\nHint Unfold update.\n```\n\n\n### S.O.S\n\nSmall-step operational semantics...\ncan be made formally in Coq code:\n\n```coq\nReserved Notation \"t1 '-->' t2\" (at level 40).\nInductive step : tm → tm → Prop :=\n  | ST_TestTru : ∀t1 t2,\n      (test tru t1 t2) --> t1\n  ...\n```\n\n\n\n### \"is stuck\" vs. \"can get stuck\"  卡住的项 vs. 将会卡住的项\n\nNoticed that the small-step semantics doesn't care about if some term would eventually get stuck.\n\n\n### Normal Forms and Values\n\n> 因为这个语言有 stuck 的情况，所以 `value != normal form` (terms cannot make progress) \n> `possible_results_of_reduction = value | stuck` \n\n```coq\nNotation step_normal_form := (normal_form step).\nDefinition stuck (t : tm) : Prop :=\n  step_normal_form t ∧ ¬value t.\n```\n\n\n### Slide Q&A 1\n\n1. Yes\n2. No   `scc zro` is a value\n3. No   is a value\n\n\n\n\n### Typing\n\n```coq\nInductive ty : Type :=\n  | Bool : ty\n  | Nat : ty.\n```\n\nNoticed that it's just a non-dependently-typed ADT, but `: ty` is written explcitly here...they are the same!\n\n\n### Typing Relations\n\nokay the funny thing...\nit make sense to use `∈` here since `:` has been used by Coq.\nbut this notation is actually represented as `\\in`. \nWe suddenly switch to LaTex mode...\n\n```coq\nReserved Notation \"'|-' t '\\in' T\" (at level 40).\n```\n\nNoticed the generic `T` here. \nIn PLT we sometimes treat them as \"magic\" _meta variable_, here we need to make the `T` explcit (we are in the meta-language).\n\n    ⊢ t1 ∈ Bool    ⊢ t2 ∈ T    ⊢ t3 ∈ T\t\n    ----------------------------------  (T_Test)\n      ⊢ test t1 then t2 else t3 ∈ T\n\n```coq\n| T_Test : ∀t1 t2 t3 T,     (** <--- explicit ∀ T **)\n       ⊢ t1 ∈ Bool →\n       ⊢ t2 ∈ T →\n       ⊢ t3 ∈ T →\n       ⊢ test t1 t2 t3 ∈ T\n```\n\n```coq\nExample has_type_1 :\n  ⊢ test fls zro (scc zro) ∈ Nat.\nProof.\n  apply T_Test.      (** <--- we already know [T] from the return type [Nat] **)\n    - apply T_Fls.   (** ⊢ _ ∈ Bool **)\n    - apply T_Zro.   (** ⊢ _ ∈ Nat  **)\n    - apply T_Scc.   (** ⊢ _ ∈ Nat  **)\n       + apply T_Zro.\nQed.\n```\n\n> (Since we've included all the constructors of the typing relation in the hint database, the `auto` tactic can actually find this proof automatically.)\n\n\n#### typing relation is a conservative (or static) approximation\n\n> 类型关系是一个保守的（或静态的）近似\n\n```coq\nExample has_type_not :\n  ¬( ⊢ test fls zro tru ∈ Bool ).\nProof.\n  intros Contra. solve_by_inverts 2. Qed.   (** 2-depth inversions **)\n```\n\n\n### `Lemma` Canonical Forms 典范形式 \n\nAs PLT. \n\n\n### Progress (可进性)\n\n```coq\nTheorem progress : ∀t T,\n  ⊢ t ∈ T →\n  value t ∨ ∃t', t --> t'.\n```\n\n> Progress vs Strong Progress?\nProgress require the \"well-typeness\"! \n\n> Induction on typing relation.\n\n\n### Slide Q&A\n\n- partial function yes\n- total function no \n  - thinking as our inference rules.\n  - we could construct some terms that no inference rules can apply and get stucked.\n\n\n### Type Preservation (维型性)\n\n```coq\nTheorem preservation : ∀t t' T,\n  ⊢ t ∈ T →   (** HT **)\n  t --> t' →  (** HE **)\n  ⊢ t' ∈ T.   (** HT' **)\n```\n\n> 按 PLT  的思路 Induction on HT，需要 inversion HE 去枚举所有情况拿到 t' 之后证明 HT'\n> 按 PFPL 的思路 Inudction on HE, 只需 inversion HT，因为 HT 是按 reduction 相反方向定义的! \n> - reduction 方向，AST top-down   e.g. (+ 5 5)   ----->   10\n> - typing    方向，AST bottom-up  e.g. |- ..:N   |-----   |- (+ 5 5):N\n\n```coq\nProof with eauto.\n  intros t t' T HT HE.\n  generalize dependent T.\n  induction HE; intros T HT;\n    inversion HT; subst...\n  apply nvalue_in_nat...  (** 除了 ST_PrdScc 全部 inversion 解决... **)\nQed.\n```\n\n> The preservation theorem is often called _subject reduction_,  -- 主语化简\n想象 term 是主语，仅仅 term 在化简，而谓语宾语不变\n\n> one might wonder whether the opposity property — _subject expansion_ — also holds. -- 主语拓张\nNo, 我们可以很容易从 `(test tru zro fls)` 证明出 `|- fls \\in Nat`. -- 停机问题 (undecidable)\n\n\n\n### Type Soundness (Type Safety)\n\n> a well-typed term never get stuck. \n\n```coq\nDefinition multistep := (multi step).  (** <--- from SmallStep **)\nNotation \"t1 '-->*' t2\" := (multistep t1 t2) (at level 40).\n\nCorollary soundness : ∀t t' T,\n  ⊢ t ∈ T →\n  t -->* t' →\n  ~(stuck t').\nProof.\n  intros t t' T HT P. induction P; intros [R S].\n  destruct (progress x T HT); auto.\n  apply IHP. apply (preservation x y T HT H).\n  unfold stuck. split; auto. Qed.\n```\n\nInduction on `-->*`, the multi-step derivation. (i.e. the reflexive-transtive closure)\n\nNoticed that in PLT, we explcitly write out what is \"non-stuck\".\nBut here is `~(stuck t')`\nthus the proof becomes:\n\n```coq\nR : step_normal_form x    (** normal form **)\nS : ~ value x             (** and not value **)\n=======================\nFalse                     (** prove this is False **)\n```\n\nThe proof is weird tho.\n\n\n\n\n"
  },
  {
    "path": "_posts/read_sf_plf/2019-03-07-sf-plf-07-STLC.md",
    "content": "---\ntitle: \"「SF-PLF」7 Stlc\"\nsubtitle: \"Programming Language Foundations - The Simply Typed Lambda-Calculus\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - SF (软件基础)\n  - PLF (编程语言基础)\n  - Coq\n  - 笔记\n---\n\nthis chapter:\n- (change to new syntax...)\n- function abstraction\n- variable binding  -- 变量绑定\n- substitution      -- 替换\n\n\nOverview\n--------\n\n\"Base Types\", only `Bool` for now.   -- 基类型\n...again, exactly following TAPL.\n\n\n```coq\nt ::= \n    | x                         variable\n    | \\x:T1.t2                  abstraction       -- haskell-ish lambda\n    | t1 t2                     application\n    | tru                       constant true\n    | fls                       constant false\n    | test t1 then t2 else t3   conditional\n\nT ::= \n    | Bool\n    | T → T                     arrow type\n\n-- example\n\\x:Bool. \\y:Bool. x\n(\\x:Bool. \\y:Bool. x) fls tru\n\\f:Bool→Bool. f (f tru)\n```\n\nSome known λ-idioms:\n> two-arg functions are higher-order one-arg fun, i.e. curried\n> no named functions yet, all \"anonymous\"  -- 匿名函数\n\n\n## Slide QA 1\n\n1. 2\n2. `Bool`, `fls`\n\n\n\n\n\n\n\nSyntax\n------\n\nFormalize syntax.\nthings are, as usual, in the `Type` level, so we can \"check\" them w/ dependent type.\n\n```coq\nInductive ty : Type :=\n  | Bool : ty\n  | Arrow : ty → ty → ty.\n\nInductive tm : Type :=\n  | var : string → tm\n  | app : tm → tm → tm\n  | abs : string → ty → tm → tm\n  | tru : tm\n  | fls : tm\n  | test : tm → tm → tm → tm.\n```\n\n> Noted that, `\\x:T.t` (formally, `abs x T t`), the argument type is explicitly annotated (not inferred.)\n\n\n另外，这里介绍了一个小 trick: 用 `Notation` (更接近 宏 ) 而非 `Defintion` 使得我们可以使用 `auto`...\n\n```coq\n(** idB = \\x:Bool. x **)\nNotation idB := (abs x Bool (var x)).\n```\n\n\n\n\n\n\nOperational Semantics\n---------------------\n\n\n### Values 值\n\n- `tru` and `fls` are values\n- what about function?\n  1. `\\x:T. t` is value iff `t` value. -- Coq \n  2. `\\x:T. t` is always value         -- most FP lang, either CBV or CBN\n\nCoq 这么做挺奇怪的，不过对 Coq 来说: \n> terms can be considered equiv up to the computation VM (在其项化简可以做到的范围内都算相等)\n> this rich the notion of Coq's value (所以 Coq 的值的概念是比一般要大的)\n\nThree ways to construct `value` (unary relation = predicate)\n\n```coq\nInductive value : tm → Prop :=\n  | v_abs : ∀x T t, value (abs x T t)\n  | v_tru : value tru\n  | v_fls : value fls.\n```\n\n\n### STLC Programs 「程序」的概念也是要定义的\n\n- _closed_    = term not refer any undefined var = __complete program__\n- _open term_ = term with _free variable_\n\n> Having made the choice not to reduce under abstractions, we don't need to worry about whether variables are values, since we'll always be reducing programs \"from the outside in,\" and that means the step relation will always be working with closed terms.\n\nif we could reduce under abstraction and variables are values... What's the implication here? 始终不懂...\n\n\n### Substitution (IMPORTANT!) 替换\n\n> `[x:=s]t` and pronounced \"substitute s for x in t.\"\n\n    (\\x:Bool. test x then tru else x) fls   ==>    test fls then tru else fls\n\n\nImportant _capture_ example:\n\n    [x:=tru] (\\x:Bool. x)  ==>  \\x:Bool. x     -- x is bound, we need α-conversion here\n                           !=>  \\x:Bool. tru\n\n\nInformal definition...\n\n    [x:=s]x               = s\n    [x:=s]y               = y                     if x ≠ y\n    [x:=s](\\x:T11. t12)   = \\x:T11. t12\n    [x:=s](\\y:T11. t12)   = \\y:T11. [x:=s]t12     if x ≠ y\n    [x:=s](t1 t2)         = ([x:=s]t1) ([x:=s]t2)\n    ...\n\nand formally:\n\n```coq\nReserved Notation \"'[' x ':=' s ']' t\" (at level 20).\nFixpoint subst (x : string) (s : tm) (t : tm) : tm :=\n  match t with\n  | var x' ⇒ if eqb_string x x' then s else t    (* <-- computational eqb_string *)\n  | abs x' T t1 ⇒ abs x' T (if eqb_string x x' then t1 else ([x:=s] t1))\n  | app t1 t2 ⇒ app ([x:=s] t1) ([x:=s] t2)\n  ...\n```\n\n> Computable `Fixpoint` means _meta-function_! (in metalanguage, Coq here)\n\n\n### 如果我们考虑用于替换掉某个变量的项 s 其本身也含有自由变量， 那么定义替换将会变得困难一点。\n\nIs `if x ≠ y` for function abstraction one sufficient?  -- 在 PLT 中我们采取了更严格的定义\n> Only safe if we only consider `s` is closed term. \n\nProf.Mtf:\n> here...it's not really \"_defining_ on closed terms\". Technically, you can still write open terms.\n> if we want, we could define the real `closed_term`...more works to prove things tho.\n\nProf.Mtf:\n> In some more rigorous setting...we might define `well_typed_term`\n> and the definition itself is the proof of `Preservation`! \n\n\n### Slide QA 2\n\n1. (3)\n\n\n### Reduction （beta-reduction） beta-归约\n\nShould be familar\n\n                    value v2\n          ----------------------------                   (ST_AppAbs)   until value, i.e. function  (β-reduction)\n          (\\x:T.t12) v2 --> [x:=v2]t12\n\n                    t1 --> t1'\n                ----------------                           (ST_App1)   reduce lhs, Function side\n                t1 t2 --> t1' t2\n\n                    value v1\n                    t2 --> t2'\n                ----------------                           (ST_App2)   reduce rhs, Arg side \n                v1 t2 --> v1 t2'\n\n\nFormally,\n(I was expecting they invents some new syntax for this one...so we only have AST)\n\n```coq\nReserved Notation \"t1 '-->' t2\" (at level 40).\nInductive step : tm → tm → Prop :=\n  | ST_AppAbs : ∀x T t12 v2,\n         value v2 →\n         (app (abs x T t12) v2) --> [x:=v2]t12\n  | ST_App1 : ∀t1 t1' t2,\n         t1 --> t1' →\n         app t1 t2 --> app t1' t2\n  | ST_App2 : ∀v1 t2 t2',\n         value v1 →\n         t2 --> t2' →\n         app v1 t2 --> app v1 t2'\n...\n```\n\n\n### Slide QA 3\n\n1. (1)  `idBB idB -> idB`\n2. (1)  `idBB (idBB idB) -> idB`\n3. if () ill-typed `idBB (notB tru) -> idBB fls ....`\n   - we don't type check in step\n4. (3)  `idB fls`\n5. NOT...ill-typed one & open term\n\n\n\n\n\n\n\n\nTyping\n------\n\n\n### Typing Contexts 类型上下文\n\nwe need something like environment but for Types.\n\n> three-place typing judgment, informally written   -- 三元类型断言\n\n    Gamma ⊢ t ∈ T\n    \n> \"under the assumptions in Gamma, the term t has the type T.\"\n\n```coq\nDefinition context := partial_map ty.\n(X ⊢> T11, Gamma) \n```\n\nWhy `partial_map` here? \nIMP can use `total_map` because it gave default value for undefined var.\n\n\n### Typing Relations\n\n\n                              Gamma x = T\n                            ----------------                            (T_Var)   look up\n                            Gamma |- x \\in T\n\n                   (x |-> T11 ; Gamma) |- t12 \\in T12\n                   ----------------------------------                   (T_Abs)   type check against context w/ arg\n                    Gamma |- \\x:T11.t12 \\in T11->T12\n\n                        Gamma |- t1 \\in T11->T12\n                          Gamma |- t2 \\in T11\n                         ----------------------                         (T_App)\n                         Gamma |- t1 t2 \\in T12\n\n\n```coq\nExample typing_example_1 :\n  empty ⊢ abs x Bool (var x) ∈ Arrow Bool Bool.\nProof.\n  apply T_Abs. apply T_Var. reflexivity. Qed.\n```\n\n\n`example_2`\n- `eapply`\n- `A` ?? looks like need need another environment to look up `A`...\n\n\n\n### Typable / Deciable\n\n\n> decidable type system = decide term if typable or not.\n> done by type checker...\n\n> can we prove...?\n> `∀ Γ e, ∃ τ, (Γ ⊢ e : τ) ∨ ¬(Γ ⊢ e : τ)` -- a type inference algorithm!\n\n> Provability in Coq witness decidabile operations.\n\n\n### show term is \"not typeable\"\n\nKeep inversion till the contradiction.\n\n\n\n"
  },
  {
    "path": "_posts/read_sf_plf/2019-03-08-sf-plf-08-STLC-prop.md",
    "content": "---\ntitle: \"「SF-PLF」8 StlcProp\"\nsubtitle: \"Programming Language Foundations - Properties of STLC\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - SF (软件基础)\n  - PLF (编程语言基础)\n  - Coq\n  - 笔记\n---\n\n基本的定理依赖关系 top-down: \n\nType Safety\n  - Progress\n    - Canonical Forms (one for each type of value)\n  - Preservation\n    - Substituion\n      - Context Invariance (in PLT, Exchange, and Weakening)\n\n\nCanonical Forms\n---------------\n\n对于我们只有 `bool` 一个 base type 的 STLC，只需要 `bool` 和 `λ`:\n\n```coq\nLemma canonical_forms_bool : ∀t,\n  empty ⊢ t ∈ Bool →\n  value t →\n  (t = tru) ∨ (t = fls).\n\nLemma canonical_forms_fun : ∀t T1 T2,\n  empty ⊢ t ∈ (Arrow T1 T2) →\n  value t →\n  ∃x u, t = abs x T1 u.\n```\n\n\n\nProgress\n--------\n\n```coq\nTheorem progress : ∀t T,\n  empty ⊢ t ∈ T →\n  value t ∨ ∃t', t --> t'.\n```\n\n类似 `Types` 章节的 `progress` 和 PLT 中的 proof. \n\n1. induction on typing relation\n2. induction on term\n\n这两个思路的证明基本一致，\n  - `auto` 上来就用把 `tru`, `fls`, `abs` 三个 `value` 的 case 干掉了，\n  - take step 的 case 则需要 witness 一个 `t'`, 这时候 Canonical Form 就派上用场了\n\n\n\n\n\nPreservation\n------------\n\n_preservation theorem_ \n  - induction on typing; prove it type-preserving after reduction/evaluation (what about induction on reduction?)\n  - `ST_AppAbs` 比较麻烦，需要做 substitution，所以我们需要证明 substituion 本身是 type-preserving...\n_substitution lemma_\n  - induction on term; prove it type-preserving after a substitution\n  - 替换会将 bound var 加入 Context，所以我们需要证明 free var 对于新的 Context 仍然是 type-preserving...\n    - 这里我们需要 the formal definition of _free var_ as well.\n_context invariance_\n  - exchange  : 交换顺序显然无影响\n  - weakening : 如果不是 override 的话，添加新变量显然对于之前的 well-typeness 无影响\n\n\n### Free Occurrences\n\n在 PLT/TAPL 中，我们将 \"free variables of an term\" 定义为一个集合 `FV(t)`. (集合是一种 computational 的概念)\n\n        FV(x) = {x}\n    FV(λx.t1) = FV(t1) ∪ FV(t2)\n    FV(t1 t2) = FV(t1) \\ {x} \n\n在这里，我们则将 \"appears_free in\" 定义为 var `x` 与 term `t` 上的二元关系: (读作 judgement 即可)\n\n```coq\nInductive appears_free_in : string → tm → Prop :=\n  | afi_var : ∀x,\n      appears_free_in x (var x)\n  | afi_app1 : ∀x t1 t2,\n      appears_free_in x t1 →\n      appears_free_in x (app t1 t2)\n  | afi_app2 : ∀x t1 t2,\n      appears_free_in x t2 →\n      appears_free_in x (app t1 t2)\n  | afi_abs : ∀x y T11 t12,\n      y ≠ x →\n      appears_free_in x t12 →\n      appears_free_in x (abs y T11 t12)\n  (** 省略 test **)\n  ... \n\nHint Constructors appears_free_in.\n\n(** a term with no free vars. 等价于 ¬(∃x,  appears_free_in x t). **) \nDefinition closed (t:tm) :=           ∀x, ¬appears_free_in x t.\n```\n\n> An _open term_ is one that _may_ contain free variables.   \n> \"Open\" precisely means \"possibly containing free variables.\"\n\n> the closed terms are a subset of the open ones. \n> closed 是 open 的子集...这样定义吗（\n\n\n### Free Vars is in Context\n\n首先我们需要一个「free var 都是 well-typed 」的 lemma\n\n```coq\nLemma free_in_context : ∀x t T Gamma,   (** 名字有一点 misleading，意思是 \"free vars is in context\" 而不是 \"var is free in context\"... **)\n   appears_free_in x t →\n   Gamma ⊢ t ∈ T →\n   ∃T', Gamma x = Some T'.\n```\n\n由此我们可以推论 所有在 empty context 下 well typed 的 term 都是 closed 得：\n\n```coq\nCorollary typable_empty__closed : ∀t T,\n    empty ⊢ t ∈ T →\n    closed t.\n```\n\n\n### Context Invariance 上下文的一些「不变式」\n\nPLT 的 Weaking 和 Exchanging 其实就对应了 Gamma 作为 `partial_map` 的 `neq` 和 `permute`\n这里，我们直接进一步地证明 「term 的 well-typeness 在『free var 的值不变的 context 变化下』是 preserving 得」: \n\n```coq\nLemma context_invariance : ∀Gamma Gamma' t T,\n    Gamma ⊢ t ∈ T →\n    (∀x, appears_free_in x t → Gamma x = Gamma' x) →    (** <-- 这句的意思是：对于 freevar，我们有其值不变。（如果没有括号就变成所有值都不变了……）**)\n    Gamma' ⊢ t ∈ T.\n```\n\n\n### Substitution!\n\n```coq\nLemma substitution_preserves_typing : ∀Gamma x U t v T,\n  (x ⊢> U ; Gamma) ⊢ t ∈ T →\n  empty ⊢ v ∈ U →              (** 这里我们其实 assume 被替换进来的项，即「参数」，是 closed 得。这是一个简化的版本 **)\n  Gamma ⊢ [x:=v]t ∈ T.\n```\n\n> 可以被看做一种交换律 (\"commutation property\")\n> 即先 type check 再 substitution 和 先 substition 再 type check 是等价的\n\nProof by induction on term __不好证，挺麻烦的__\n\n\n### Finally, Preservation\n\n```coq\nTheorem preservation : ∀t t' T,\n  empty ⊢ t ∈ T →\n  t --> t' →\n  empty ⊢ t' ∈ T.\n```\n\n\n### Not subject expansion\n\n```coq\nTheorem not_subject_expansion:\n  ~(forall t t' T, t --> t' /\\ empty |- t' \\in T -> empty |- t \\in T).\n```\n\n    (app (abs x (Arrow Bool Bool) tru) tru)  -- 考虑 term \n\n    (λx:Bool->Bool . tru) tru   -->   tru    -- 可以 step\n                        empty   |-   Bool    -- step 后 well-typed\n\n    empty |-/-  (λx:Bool->Bool . tru) tru    -- 但是原 term 显然 ill-typed\n\n\n\n\nType Soundness\n--------------\n\n```coq\n(** stuck 即在不是 value 的时候无法 step **)\nDefinition stuck (t:tm) : Prop :=\n  (normal_form step) t ∧ ¬value t.\n\n(** well-typed term never get stuck! **)\nCorollary soundness : ∀t t' T,\n  empty ⊢ t ∈ T →\n  t -->* t' →\n  ~(stuck t').\n```\n\n\n\nUniqueness of Types\n-------------------\n\n> 这里的 Uniqueness 与 Right-unique / deterministic / functional 其实都是相同的内涵\n\n```coq\nTheorem unique_types : ∀Gamma e T T',\n  Gamma ⊢ e ∈ T →\n  Gamma ⊢ e ∈ T' →\n  T = T'.\n```\n\n\n\n\n\nAdditional Exercises\n--------------------\n\n### STLC with Arithmetic \n\n> only `Nat`...这样就不用管 the interaction between `Bool` and `Nat` \n\n```coq\nInductive ty : Type :=\n  | Arrow : ty → ty → ty\n  | Nat : ty.            (** <-- the only concrete base type **)\n\n\nInductive tm : Type :=\n  | var : string → tm\n  | app : tm → tm → tm\n  | abs : string → ty → tm → tm\n  | const : nat → tm     (** <-- 居然用 metalang 的 nat 而非 zro **)\n  | scc : tm → tm\n  | prd : tm → tm\n  | mlt : tm → tm → tm\n  | test0 : tm → tm → tm → tm.\n```\n\n更多拓展见下一章 `MoreStlc.v` \n\n\n"
  },
  {
    "path": "_posts/read_sf_plf/2019-03-09-sf-plf-09-more-STLC.md",
    "content": "---\ntitle: \"「SF-PLF」9 MoreStlc\"\nsubtitle: \"Programming Language Foundations - More on The Simply Typed Lambda-Calculus\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - SF (软件基础)\n  - PLF (编程语言基础)\n  - Coq\n  - 笔记\n---\n\n> make the STLC into a PL!\n\n\n\nSimple Extensions to STLC\n-------------------------\n\n> 其实这一部分我好像没有任何必要做笔记……\n\n\n### Numbers\n\nSee `StlcProp.v` exercise `stlc_arith`.\n\n\n\n### Let Bindings\n\n- In PLT slide, we treat `let x   = t1 in e` as a derived form of `(λx   . e) t1`.\n- In PLT langF, we treat `let x:T = t1 in e` as a derived form of `(λx:T . e) t1`. (both require explicit type annotation)\n\nSF here, same as TaPL, treat it _less derived_ by _compute the type `T1` from `t1`. \n- but TaPL treat it by desugar to `λ` later on, here we directly \"execute\" it via substituion.\n\n我想这里有一个原因是， `λ` 必须要可以独立被 typed，但是这时候我们还没有 `t1`，无法计算出 `T1`。而 `let` 的形式中包括了 `t1`，所以可以直接计算:\n\n```coq\nt ::=                Terms\n    | ...\n    | let x=t in t      let-binding\n```\n\n    Reduction:\n\n                                 t1 --> t1'\n                     ----------------------------------               (ST_Let1)\n                     let x=t1 in t2 --> let x=t1' in t2\n\n                        ----------------------------              (ST_LetValue)  <-- substitute as λ\n                        let x=v1 in t2 --> [x:=v1]t2\n\n    Typing:\n\n             Gamma |- t1 \\in T1      x|->T1; Gamma |- t2 \\in T2\n             --------------------------------------------------        (T_Let)\n                        Gamma |- let x=t1 in t2 \\in T2\n\n\n\n### Pairs (Product Type)\n\n\n```coq\nt ::=                Terms\n    | ...\n    | (t,t)             pair\n    | t.fst             first projection\n    | t.snd             second projection\n\nv ::=                Values\n    | ...\n    | (v,v)             pair value\n\nT ::=                Types\n    | ...\n    | T * T             product type\n```\n\n    Reduction:\n\n                              t1 --> t1'\n                         --------------------                        (ST_Pair1)\n                         (t1,t2) --> (t1',t2)\n\n                              t2 --> t2'\n                         --------------------                        (ST_Pair2)\n                         (v1,t2) --> (v1,t2')\n\n                              t1 --> t1'\n                          ------------------                          (ST_Fst1)\n                          t1.fst --> t1'.fst\n\n                          ------------------                       (ST_FstPair)\n                          (v1,v2).fst --> v1\n\n                              t1 --> t1'\n                          ------------------                          (ST_Snd1)\n                          t1.snd --> t1'.snd\n\n                          ------------------                       (ST_SndPair)\n                          (v1,v2).snd --> v2\n\n\n    Typing:\n\n               Gamma |- t1 \\in T1     Gamma |- t2 \\in T2\n               -----------------------------------------               (T_Pair)\n                       Gamma |- (t1,t2) \\in T1*T2\n\n                        Gamma |- t \\in T1*T2\n                        ---------------------                          (T_Fst)\n                        Gamma |- t.fst \\in T1\n\n                        Gamma |- t \\in T1*T2\n                        ---------------------                          (T_Snd)\n                        Gamma |- t.snd \\in T2\n\n\n\n### Unit (Singleton Type) 单元类型\n\n`unit` is the only value/normal form of type `Unit`, but not the only term (also any terms that would reduce to `unit`)\n\n\n```coq\nt ::=                Terms\n    | ...\n    | unit              unit              -- often written `()` as well\n\nv ::=                Values\n    | ...\n    | unit              unit value\n\nT ::=                Types\n    | ...\n    | Unit              unit type         -- Haskell even write this `()`\n```\n\n    No reduction rule!\n\n    Typing:\n\n                         ----------------------                        (T_Unit)\n                         Gamma |- unit \\in Unit\n\n\n> wouldn't every computation _living in_ such a type be trivial?\n> 难道不是每个计算都不会在这样的类型中_居留_吗？\n\n> Where Unit really comes in handy is in richer languages with side effects\n> 在更丰富的语言中，使用 Unit 类型来处理副作用（side effect） 会很方便\n\n\n\n### Sum Type (Disjointed Union)\n\n> deal with values that can take two distinct forms -- binary sum type\n> 两个截然不同的 ... \"二元和\"类型\n\n> We create elements of these types by _tagging_ elements of the component types\n> 我们在创建这些类型的值时，会为值_标记_上其\"成分\"类型\n\n标签 `inl`, `inr` 可以看做为函数，即 _Data Constructor_\n\n    inl : Nat  -> Nat + Bool\n    inr : Bool -> Nat + Bool\n\n> that _\"inject\"_ (注入) elements of `Nat` or `Bool` into the left and right components of the sum type `Nat+Bool`\n\n不过这里并没有把他们作为 function 来形式化，而是把 `inl` `inr` 作为关键字，把 `inl t` `inr t` 作为 primitive syntactic form...\n\n\n- In PLT slide, we use `L          (e)` and say the `T2` would be \"guessed\" to produce `T1 + T2`, as _TaPL option 1_\n- In PLT langF, we use `L [T1 +T2] (e)` i.e. provide a explicit type annotation for the sum type, as _TaPL option 3_ (ascription)\n\nSF here, use something in the middle: \n- you provide only `T2` to `L(t1)` and `T1` would be computed from `t1` to form the `T1 + T2`. \n\n\n```coq\nt ::=                Terms\n    | ...\n    | inl T t           tagging (left)\n    | inr T t           tagging (right)\n    | case t of         case\n        inl x => t\n      | inr x => t\n\nv ::=                Values\n    | ...\n    | inl T v           tagged value (left)\n    | inr T v           tagged value (right)\n\nT ::=                Types\n    | ...\n    | T + T             sum type\n```\n\n    Reduction:\n\n                               t1 --> t1'\n                        ------------------------                       (ST_Inl)\n                        inl T2 t1 --> inl T2 t1'\n\n                               t2 --> t2'\n                        ------------------------                       (ST_Inr)\n                        inr T1 t2 --> inr T1 t2'\n\n                               t0 --> t0'\n               -------------------------------------------            (ST_Case)\n                case t0 of inl x1 => t1 | inr x2 => t2 -->\n               case t0' of inl x1 => t1 | inr x2 => t2\n\n            -----------------------------------------------        (ST_CaseInl)\n            case (inl T2 v1) of inl x1 => t1 | inr x2 => t2\n                           -->  [x1:=v1]t1\n\n            -----------------------------------------------        (ST_CaseInr)\n            case (inr T1 v2) of inl x1 => t1 | inr x2 => t2\n                           -->  [x2:=v1]t2\n\n    Typing:\n\n                          Gamma |- t1 \\in T1\n                   ------------------------------                       (T_Inl)\n                   Gamma |- inl T2 t1 \\in T1 + T2\n\n                          Gamma |- t2 \\in T2\n                   -------------------------------                      (T_Inr)\n                    Gamma |- inr T1 t2 \\in T1 + T2\n\n                        Gamma |- t \\in T1+T2\n                     x1|->T1; Gamma |- t1 \\in T\n                     x2|->T2; Gamma |- t2 \\in T\n         ----------------------------------------------------          (T_Case)\n         Gamma |- case t of inl x1 => t1 | inr x2 => t2 \\in T\n\n\n\n### Lists\n\n\n> The typing features we have seen can be classified into \n> - 基本类型   _base types_ like `Bool`, and\n> - 类型构造子 _type constructors_ like `→` and `*` that build new types from old ones.\n\n> In principle, we could encode lists using pairs, sums and _recursive types_. (and _type operator_ to give the type a name in SystemFω)\n\n> 但是 recursive type 太 non-trivial 了……于是我们直接处理为一个特殊的类型吧\n\n- in PLT slide, again, we omit the type and simply write `nil : List T`\n  - 有趣的是, Prof.Mtf 并不满意这个，因为会有 `hd nil` 这样 stuck 的可能，所以额外给了一个用 `unlist` (unempty list) 的 def\n\n- in PLT langF, we did use pairs + sums + recursive types: \n  - langF `nil : all('a . rec('b . unit + ('a * 'b)))`\n  - StlcE `nil : ∀α     . µβ     . unit + (α ∗ β)` \n\n- in TaPL ch11, we manually provide `T` to all term (data constructor)\n  - but actually, only `nil` need it! (others can be inferred by argument)\n  \nand that's we did for SF here! \n\n\n```coq\nt ::=                Terms\n    | ...\n    | nil T                          -- nil need explicit type annotation\n    | cons t t\n    | lcase t of nil  => t           -- a special case for list\n               | x::x => t\n\nv ::=                Values\n    | ...\n    | nil T             nil value\n    | cons v v          cons value\n\nT ::=                Types\n    | ...\n    | List T            list of Ts\n```\n\n    Reduction:\n\n                                t1 --> t1'\n                       --------------------------                    (ST_Cons1)\n                       cons t1 t2 --> cons t1' t2\n\n                                t2 --> t2'\n                       --------------------------                    (ST_Cons2)\n                       cons v1 t2 --> cons v1 t2'\n\n                              t1 --> t1'\n                -------------------------------------------         (ST_Lcase1)\n                 (lcase t1 of nil => t2 | xh::xt => t3) -->\n                (lcase t1' of nil => t2 | xh::xt => t3)\n\n               -----------------------------------------          (ST_LcaseNil)\n               (lcase nil T of nil => t2 | xh::xt => t3)\n                                --> t2\n\n            ------------------------------------------------     (ST_LcaseCons)\n            (lcase (cons vh vt) of nil => t2 | xh::xt => t3)\n                          --> [xh:=vh,xt:=vt]t3                  -- multiple substi\n\n\n    Typing:\n\n                        -------------------------                       (T_Nil)\n                        Gamma |- nil T \\in List T\n\n             Gamma |- t1 \\in T      Gamma |- t2 \\in List T\n             ---------------------------------------------             (T_Cons)\n                    Gamma |- cons t1 t2 \\in List T\n\n                        Gamma |- t1 \\in List T1\n                        Gamma |- t2 \\in T\n                (h|->T1; t|->List T1; Gamma) |- t3 \\in T\n          ---------------------------------------------------         (T_Lcase)\n          Gamma |- (lcase t1 of nil => t2 | h::t => t3) \\in T\n\n\n\n\n### General Recursion (Fixpoint)\n\n通用的递归，而非 primitive recursion (PFPL)\n\n```hs\nfact = \\x:Nat . if x=0 then 1 else x * (fact (pred x)))\n```\n\n这个在 Stlc 中不被允许，因为我们在定义 `fact` 的过程中发现了一个 free 的 `fact`，要么未定义，要么不是自己。\n所以我们需要 `Fixpoint` \n\n```hs\nfact = fix (\\fact:Nat->Nat. \n       \\x:Nat . if x=0 then 1 else x * (fact (pred x)))\n```\n\n\n```coq\nt ::=                Terms\n    | ...\n    | fix t             fixed-point operator\n```\n\n   Reduction:\n\n                                t1 --> t1'\n                            ------------------                        (ST_Fix1)\n                            fix t1 --> fix t1'\n\n               --------------------------------------------         (ST_FixAbs)\n               fix (\\xf:T1.t2) --> [xf:=fix (\\xf:T1.t2)] t2         -- fix f = f (fix f)\n\n   Typing:\n\n                           Gamma |- t1 \\in T1->T1\n                           ----------------------                       (T_Fix)\n                           Gamma |- fix t1 \\in T1\n\n\n\n### Records \n\n这里的定义非常 informal:\n\n\n```coq\nt ::=                          Terms\n    | ...\n    | {i1=t1, ..., in=tn}         record\n    | t.i                         projection\n\nv ::=                          Values\n    | ...\n    | {i1=v1, ..., in=vn}         record value\n\nT ::=                          Types\n    | ...\n    | {i1:T1, ..., in:Tn}         record type\n```\n\n    Reduction:\n\n                              ti --> ti'\n                 ------------------------------------                  (ST_Rcd)\n                     {i1=v1, ..., im=vm, in=ti , ...}\n                 --> {i1=v1, ..., im=vm, in=ti', ...}\n\n                              t1 --> t1'\n                            --------------                           (ST_Proj1)\n                            t1.i --> t1'.i\n\n                      -------------------------                    (ST_ProjRcd)\n                      {..., i=vi, ...}.i --> vi\n\n    Typing:\n\n            Gamma |- t1 \\in T1     ...     Gamma |- tn \\in Tn\n          ----------------------------------------------------          (T_Rcd)\n          Gamma |- {i1=t1, ..., in=tn} \\in {i1:T1, ..., in:Tn}\n\n                    Gamma |- t \\in {..., i:Ti, ...}\n                    -------------------------------                    (T_Proj)\n                          Gamma |- t.i \\in Ti\n\n\n### 其他\n\n提了一嘴 \n\n- Variant \n- Recursive type `μ`\n\n加起来就可以 \n> give us enough mechanism to build _arbitrary inductive data types_ like lists and trees from scratch \n\nBasically \n\nADT = Unit + Product + Sum (Variant) + Function (Expo)\n\n但是 Coq 的 `Inductive` 还需要进一步的 Pi (Dependent Product), Sigma (Dependent Sum).\n\n\n\n\nExercise: Formalizing the Extensions\n------------------------------------\n\n### STLCE definitions\n\n基本上就是把上面的 rule 用 AST 写进来\n\n\n\n### STLCE examples\n\n> a bit of Coq hackery to automate searching for typing derivation\n\n基本上就是自动化的 pattern matching + tactics\n\n```coq\nHint Extern 2 (has_type _ (app _ _) _) =>\n  eapply T_App; auto.\n\nHint Extern 2 (has_type _ (tlcase _ _ _ _ _) _) =>\n  eapply T_Lcase; auto.\n\nHint Extern 2 (_ = _) => compute; reflexivity.\n```\n\n\n效果非常酷：typecheck 只需要 `eauto`，reduction 只需要 `normalize`.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "_posts/read_sf_plf/2019-03-10-sf-plf-10-subtyping.md",
    "content": "---\ntitle: \"「SF-PLF」10 Sub\"\nsubtitle: \"Programming Language Foundations - Subtyping (子类型化)\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - SF (软件基础)\n  - PLF (编程语言基础)\n  - Coq\n  - 笔记\n---\n\n\n\nConcepts\n--------\n\n\n\n### The Subsumption Rule\n\n\n### The Subtype Relation\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n### Slide QA1\n\nRecord Subtyping... \n\nrow type\n\n\nindex? record impl as list\n\n\nwidth/depth/permulation\n- multiple step rules\n\n\n\n---\n\n\nJava\n\n1. class - no index (thinking about offset)\n\nhaving both width/permulation subtyping make impl slow\n- OOP - hmm\n- ML has no permulation - for perf reason (static structure) as C\n\nML has depth?\n- a little bit by equality\n\n\nOCaml objection has all three\n\n\n### Slide QA2\n\nLooking at Contravariant!\n\n1. (2) `{i1:S,i2:T}→U <: {i1:S,i2:T,i3:V}→U`\n\n2. (4) `{i1:T,i2:V,i3:V} <: {i1:S,i2:U} * {i3:V}` is interesting:\n\nthe interesting thing is, why don't we make some subtyping rules for that as well?\n\n- there are definitely _code_ can do that\n- their _runtime_ semantics are different tho they carry same information\n- __coercion__ can used for that\n\n3 and 4. (5) ...\n\n\nA <: Top   =>   Top -> A <: A -> A  -- contravariant\n\nif we only care `(A*T)`, can use `T:Top`\n\nbut to type the whole thing `: A`\n\n`Top -> A`?\nbut noticed that we said `\\z:A.z`\n\ncan we pass `A -> A` into `Top -> A`? \n      more specific        more general\n      \nsmallest -> most specific -> `A -> A`\nlargest  -> most specific -> `Top -> A`\n\n\n5. \n\"The type Bool has no proper subtypes.\" (I.e., the only type smaller than Bool is Bool itself.)\nTure unless we have Bottom\n\nhmm seems like `Bottom` in subtyping is different with Empty/Void, which is closer to logical `Bottom ⊥` since Bottom here is subtyping of everything..\nOH they are the same: (nice)\n> <https://en.wikipedia.org/wiki/Bottom_type>\n\n6. True\n\n\n\n### Inversion Lemmas for Subtyping\n\n`inversion` doesn't lose information, `induction` does.\n\nauto rememeber?? --- dependent induction\nhetergeous equaltiy\n\n\n\nIn soundness proof\n\n- subtyping only affects Canonical Forms + T_Sub case in induction\n\n\n> Lemma: If Gamma ⊢ \\x:S1.t2 ∈ T, then there is a type S2 such that x⊢>S1; Gamma ⊢ t2 ∈ S2 and S1 → S2 <: T.\n\nwhy `T` not arrow? Top...\n\n\nif including Bottom...many proof becomes hard, canonical form need to say...might be Bottom?\n\n> no, no value has type Bottom (Void)...\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "_posts/read_sf_plf/2019-03-11-sf-plf-11-typechecking.md",
    "content": "---\ntitle: \"「SF-PLF」11. TypeChecking\"\nsubtitle: \"Programming Language Foundations - A Typechecker for STLC\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - SF (软件基础)\n  - PLF (编程语言基础)\n  - Coq\n  - 笔记\n---\n\n> The `has_type` relation is good but doesn't give us a _executable algorithm_ -- 不是一个算法\n> but it's _syntax directed_, just one typing rule for one term (unique typing) -- translate into function!\n\n\nComparing Types\n---------------\n\n首先我们需要 check equality for types.\n这里非常简单，如果是 SystemF 会麻烦很多，对 `∀` 要做 local nameless 或者 alpha renaming:\n\n```coq\nFixpoint eqb_ty (T1 T2:ty) : bool :=\n  match T1,T2 with\n  | Bool, Bool ⇒\n      true\n  | Arrow T11 T12, Arrow T21 T22 ⇒\n      andb (eqb_ty T11 T21) (eqb_ty T12 T22)\n  | _,_ ⇒\n      false\n  end.\n```\n\n然后我们需要一个 refl 和一个 reflection，准确得说：「define equality by computation」，反方向用 refl 即可易证\n\n```coq\nLemma eqb_ty_refl : ∀T1,\n  eqb_ty T1 T1 = true.\n\nLemma eqb_ty__eq : ∀T1 T2,\n  eqb_ty T1 T2 = true → T1 = T2.\n```\n\n\n\nThe Typechecker\n---------------\n\n直接 syntax directed，不过麻烦的是需要 pattern matching `option`...\n\n```coq\nFixpoint type_check (Gamma : context) (t : tm) : option ty :=\n  match t with\n  | var x =>\n      Gamma x\n  | abs x T11 t12 =>\n      match type_check (update Gamma x T11) t12 with     (** <-- 对应 t12 的 rule **)\n      | Some T12 => Some (Arrow T11 T12)                 \n      | _ => None\n      end\n  | app t1 t2 =>\n      match type_check Gamma t1, type_check Gamma t2 with\n      | Some (Arrow T11 T12),Some T2 =>\n          if eqb_ty T11 T2 then Some T12 else None       (** eqb_ty 见下文 **)\n      | _,_ => None\n      end\n  ...\n```\n\n在课堂时提到关于 `eqb_ty` 的一个细节（我以前也经常犯，在 ML/Haskell 中……）：\n我们能不能在 pattern matching 里支持「用同一个 binding 来 imply 说他们两需要 be equal」？\n\n```coq\n(** instead of this **)\n| Some (Arrow T11 T12),Some T2 => if eqb_ty T11 T2 then ...\n\n(** can we do this? **)\n| Some (Arrow T   T' ),Some T  => ...\n```\n\n> the answer is __NO__ because this demands a _decidable equality_. \n> 我好奇的是，用 typeclass 是不是就可以 bake in 这个功能了？尤其是在 Coq function 还是 total 的情况下\n\n\n\n\n\n\nDigression: Improving the Notation\n----------------------------------\n\n这里我们可以自己定义一个 Haskell `do` notation 风格的 _monadic_ notation:\n\n```coq\nNotation \" x <- e1 ;; e2\" := (match e1 with\n                              | Some x ⇒ e2\n                              | None ⇒ None\n                              end)\n         (right associativity, at level 60).\n\nNotation \" 'return' e \"\n  := (Some e) (at level 60).\n\nNotation \" 'fail' \"\n  := None.\n```\n\n好看一些吧反正：\n\n```coq\nFixpoint type_check (Gamma : context) (t : tm) : option ty :=\n  match t with\n  | var x ⇒\n      Gamma x \n  | abs x T11 t12 ⇒\n      T12 <- type_check (update Gamma x T11) t12 ;;\n      return (Arrow T11 T12)\n  | app t1 t2 ⇒\n      T1 <- type_check Gamma t1 ;;\n      T2 <- type_check Gamma t2 ;;\n      match T1 with \n      | Arrow T11 T12 ⇒ if eqb_ty T11 T2 then return T12 else fail\n      | _ ⇒ fail\n      end\n```\n\n\nProperties\n----------\n\n最后我们需要验证一下算法的正确性：\n这里的 soundness 和 completess 都是围绕 \"typechecking function ~ typing relation inference rule\" 这组关系来说的：\n\n```coq\nTheorem type_checking_sound : ∀Gamma t T,\n  type_check Gamma t = Some T → has_type Gamma t T.\n\nTheorem type_checking_complete : ∀Gamma t T,\n  has_type Gamma t T → type_check Gamma t = Some T.\n\n```\n\n\n\nExercise\n--------\n\n给 `MoreStlc.v` 里的 StlcE 写 typechecker, 然后 prove soundness / completeness （过程中用了非常 mega 的 tactics）\n\n```coq\n(** 还不能这么写 **)\n| fst p =>\n    (Prod T1 T2) <- type_check Gamma p ;;\n\n\n(** 要这样……感觉是 notation 的缘故？并且要提供 fallback case 才能通过 exhaustive check 是真的 **)\n| fst p =>\n    Tp <- type_check Gamma p ;;\n    match Tp with\n    | (Prod T1 T2) => T1\n    | _ => fail\n    end.\n```\n\n\nExtra Exercise (Prof.Mtf)\n-------------------------\n\n> I believe this part of exercise was added by Prof. Fluet (not found in SF website version)\n\n给 `MoreStlc.v` 的 operational semantics 写 Interpreter (`stepf`), 然后 prove soundness / completeness... \n\n\n### `step` vs. `stepf` \n\n首先我们定义了 `value` 关系的函数版本 `valuef`，\n然后我们定义 `step` 关系的函数版本 `stepf`:\n\n以 pure STLC 为例：\n\n```coq\nInductive step : tm -> tm -> Prop :=\n  | ST_AppAbs : forall x T11 t12 v2,\n         value v2 ->\n         (app (abs x T11 t12) v2) --> [x:=v2]t12\n  | ST_App1 : forall t1 t1' t2,\n         t1 --> t1' ->\n         (app t1 t2) --> (app t1' t2)\n  | ST_App2 : forall v1 t2 t2',\n         value v1 ->\n         t2 --> t2' ->\n         (app v1 t2) --> (app v1 t2')\n```\n```coq\nFixpoint stepf (t : tm) : option tm :=\n  match t with\n  | var x        => None (* We only define step for closed terms *)\n  | abs x1 T1 t2 => None (* Abstraction is a value *)\n  | app t1 t2    =>\n    match stepf t1, stepf t2, t1 with\n    | Some t1', _       , _           =>                     Some (app t1' t2)\n    | None    , Some t2', _           => assert (valuef t1) (Some (app t1 t2')) (* otherwise [t1]      is a normal form *)\n    | None    , None    , abs x T t11 => assert (valuef t2) (Some ([x:=t2]t11)) (* otherwise [t1], [t2] are normal forms *)\n    | _       , _       , _           =>                     None\n    end\n\nDefinition assert (b : bool) (a : option tm) : option tm := if b then a else None.\n```\n\n1. 对于关系，一直就是 implicitly applied 的，在可用时即使用。\n   对于函数，我们需要手动指定 match 的顺序\n\n2. `stepf t1 => None` 只代表这是一个 `normal form`，但不一定就是 `value`，还有可能是 stuck 了，所以我们需要额外的 `assert`ion. (失败时返回异常)\n   __dynamics__ 本身与 __statics__ 是正交的，在 `typecheck` 之后我们可以有 `progress`，但是现在还没有\n\n\n\n### Soundness\n\n```coq\nTheorem sound_stepf : forall t t',\n    stepf t = Some t'  ->  t --> t'.\n```\n\n证明用了一个 given 的非常夸张的 automation...\n\n不过帮助我找到了 `stepf` 和 `step` 的多处 inconsistency: \n- 3 次做 `subst` 时依赖的 `valuef` 不能省\n- `valuef pair` 该怎么写才合适？\n  最后把 `step` 中的 `value p ->` 改成了 `value v1 -> value v2 ->`，\n  因为 `valuef (pair v1 v2)` 出来的 `valuef v1 && valuef v2` 比较麻烦。\n  但底线是：__两者必须 consistent！__ 这时就能感受到 Formal Methods 的严谨了。\n\n\n### Completeness\n\n发现了 pair 实现漏了 2 个 case……然后才发现了 `Soundness` 自动化中的 `valuef pair` 问题\n\n\n\nExtra (Mentioned)\n-----------------\n-----\n\n[Church Style vs. Curry Style](https://lispcast.com/church-vs-curry-types/)\n[Rice's Theorem](https://en.wikipedia.org/wiki/Rice%27s_theorem)\n\nCakeML \n- prove correctness of ML lang compiler\n- latest paper on verifying GC\n"
  },
  {
    "path": "_posts/read_sf_plf/2019-03-12-sf-plf-12-records.md",
    "content": "---\ntitle: \"「SF-PLF」12 Records\"\nsubtitle: \"Programming Language Foundations - Adding Records To STLC\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - SF (软件基础)\n  - PLF (编程语言基础)\n  - Coq\n  - 笔记\n---\n\n\n## Adding Records\n\n\n```coq\nt ::=                          Terms:\n    | {i1=t1, ..., in=tn}         record\n    | t.i                         projection\n    | ...\n\nv ::=                          Values:\n    | {i1=v1, ..., in=vn}         record value\n    | ...\n\nT ::=                          Types:\n    | {i1:T1, ..., in:Tn}         record type\n    | ...\n```\n\n\n## Formalizing Records\n\n\n\n\n\n"
  },
  {
    "path": "_posts/read_sf_plf/2019-03-13-sf-plf-13-references.md",
    "content": "---\ntitle: \"「SF-PLF」13 References\"\nsubtitle: \"Programming Language Foundations - Typing Mutable References\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - SF (软件基础)\n  - PLF (编程语言基础)\n  - Coq\n  - 笔记\n---\n\n> Hux: this chapter is very similar to TAPL - ch13 References\n> But under a \"formal verification\" concept, it's more interesting and practical and push you to think about it!\n\n\n_computational effects_ - \"side effects\" of computation - _impure_ features\n- assign to mutable variables (reference cells, arrays, mutable record fields, etc.)\n- perform input and output to files, displays, or network connections; \n- make non-local transfers of control via exceptions, jumps, or continuations; \n- engage in inter-process synchronization and communication\n\n\n> The main extension will be dealing explicitly with a \n> - _store_ (or _heap_) and \n> - _pointers_ (or _reference_) that name _store locations_, or _address_...\n\ninteresting refinement: type preservation\n\n\n\nDefinition\n----------\n\nforms of assignments:\n- rare : Gallina   - No \n- some : ML family - Explicit _reference_ and _dereference_\n- most : C  family - Implicit ...\n\nFor formal study, use ML's model.\n\n\n\nSyntax\n------\n\n### Types & Terms\n\n```coq\nT ::= \n    | Nat\n    | Unit\n    | T → T\n    | Ref T\n\nt ::= \n    | ...                Terms\n    | ref t              allocation\n    | !t                 dereference\n    | t := t             assignment\n    | l                  location\n```\n```coq\nInductive ty : Type :=\n  | Nat : ty\n  | Unit : ty\n  | Arrow : ty → ty → ty\n  | Ref : ty → ty.\n\nInductive tm : Type :=\n  (* STLC with numbers: *)\n  ...\n  (* New terms: *)\n  | unit : tm\n  | ref : tm → tm\n  | deref : tm → tm\n  | assign : tm → tm → tm\n  | loc : nat → tm.         (** 这里表示 l 的方式是 wrap 一个 nat as loc **)\n```\n\n\n### Typing\n\n\n                           Gamma |- t1 : T1\n                       ------------------------                         (T_Ref)\n                       Gamma |- ref t1 : Ref T1\n\n                        Gamma |- t1 : Ref T11\n                        ---------------------                         (T_Deref)\n                          Gamma |- !t1 : T11\n\n                        Gamma |- t1 : Ref T11\n                          Gamma |- t2 : T11\n                       ------------------------                      (T_Assign)\n                       Gamma |- t1 := t2 : Unit\n\n\n### Values and Substitution\n\n```coq\nInductive value : tm → Prop :=\n  ...\n  | v_unit :     value unit\n  | v_loc  : ∀l, value (loc l).  (* <-- 注意这里是一个 Π (l:nat) . value (loc l) *)\n```\n\n```coq\nFixpoint subst (x:string) (s:tm) (t:tm) : tm :=\n  match t with\n  ...\n  | unit         ⇒ t\n  | ref t1       ⇒ ref (subst x s t1)\n  | deref t1     ⇒ deref (subst x s t1)\n  | assign t1 t2 ⇒ assign (subst x s t1) (subst x s t2)\n  | loc _        ⇒ t\n  end.\n```\n\n\n\n\nPragmatics\n----------\n\n\n### Side Effects and Sequencing\n\n    r:=succ(!r); !r\n\ncan be desugar to\n\n    (\\x:Unit. !r) (r:=succ(!r)).\n    \nthen we can write some \"imperative programming\"\n\n    r:=succ(!r); \n    r:=succ(!r); \n    r:=succ(!r); \n    !r\n\n\n### References and Aliasing\n\n_shared reference_ brings __shared state_\n\n    let r = ref 5 in\n    let s = r in\n    s := 82;\n    (!r)+1\n\n\n### Shared State\n\n_thunks_ as _methods_ \n\n```haskell\n\n    let c = ref 0 in\n    let incc = \\_:Unit. (c := succ (!c); !c) in\n    let decc = \\_:Unit. (c := pred (!c); !c) in (\n      incc unit; \n      incc unit;          -- in real PL: the concrete syntax is `incc()`\n      decc unit\n    )\n\n```\n\n\n### Objects \n\n_constructor_ and _encapsulation_!\n\n```haskell\n\n    newcounter =\n      \\_:Unit.            -- add `(self, init_val)` would make it more \"real\"\n        let c = ref 0 in  -- private and only accessible via closure (特权方法)\n        let incc = \\_:Unit. (c := succ (!c); !c) in\n        let decc = \\_:Unit. (c := pred (!c); !c) in\n        { i=incc, \n          d=decc  }       -- return a \"record\", or \"struct\", or \"object\"!\n          \n```\n\n\n### References to Compound Types (e.g. Function Type)\n\nPreviously, we use _closure_ to represent _map_, with _functional update_\n这里的\"数组\" （这个到底算不算数组估计都有争议，虽然的确提供了 index 但是这个显然是 O(n) 都不知道算不算 random access...\n并不是 in-place update 里面的数据的，仅仅是一个 `ref` 包住的 map 而已 （仅仅是多了可以 shared\n\n其实或许 `list (ref nat)` 也可以表达数组？ 反正都是 O(n) 每次都 linear search 也一样……\n\n```haskell\n\n    newarray = \\_:Unit. ref (\\n:Nat.0)\n    lookup = \\a:NatArray. \\n:Nat. (!a) n   \n    update = \\a:NatArray. \\m:Nat. \\v:Nat.\n               let oldf = !a in\n               a := (\\n:Nat. if equal m n then v else oldf n);\n\n```\n\n\n### Null References\n\n_nullptr_!\n\nDeref a nullptr:\n- exception in Java/C#\n- insecure in C/C++     <-- violate memory safety!!\n\n```haskell\n\n    type Option T   = Unit + T\n    type Nullable T = Option (Ref T)\n\n```\n\n\nWhy is `Option` outside?\nthink about C, `nullptr` is A special _const_ location, like `Unit` (`None` in terms of datacon) here.\n\n\n### Garbage Collection\n\nlast issue: store _de-allocation_ \n\n> w/o GC, extremely difficult to achieve type safety...if a primitive for \"explicit deallocation\" provided\n> one can easily create _dangling reference_ i.e. references -> deleted\n\nOne type-unsafe example: (pseudo code)\n\n```haskell\n\n   a : Ref Nat = ref 1;       -- alloc loc 0\n   free(a);                   -- free  loc 0\n   b : Ref Bool = ref True;   -- alloc loc 0\n   \n   a := !a + 1                -- BOOM!\n\n```\n\n\n\n\n\nOperational Semantics\n---------------------\n\n\n### Locations\n\n> what should be the _values_ of type `Ref T`? \n\n`ref` allocate some memory/storage!\n\n> run-time store is essentially big array of bytes.\n> different datatype need to allocate different size of space (region)\n\n> we think store as _array of values_, _abstracting away different size of different values_\n> we use the word _location_ here to prevent from modeling _pointer arithmetic_, which is un-trackable by most type system\n\nlocation `n` is `float` doesn't tell you anything about location `n+4`...\n \n\n\n### Stores\n\nwe defined `replace` as `Fixpoint` since it's computational and easier. The consequence is it has to be total.\n\n\n\n### Reduction\n\n\n   \n\n\nTyping\n------\n\ntyping context:\n\n```coq\nDefinition context := partial_map ty.\n```\n\n### Store typings\n\nwhy not just make a _context_ a map of pair? \nwe don't want to complicate the dynamics of language,\nand this store typing is only for type check.\n\n\n\n### The Typing Relation\n\n\n\n\n\n\nProperties\n----------\n\n### Well-Typed Stores\n\n### Extending Store Typings\n\n### Preservation, Finally\n\n### Substitution Lemma\n\n### Assignment Preserves Store Typing\n\n### Weakening for Stores\n\n### Preservation!\n\n### Progress\n\n\n\n\n\nReferences and Nontermination\n-----------------------------\n"
  },
  {
    "path": "_posts/read_sf_plf/2019-03-14-sf-plf-14-record-sub.md",
    "content": "---\ntitle: \"「SF-PLF」14 RecordSub\"\nsubtitle: \"Programming Language Foundations - Subtyping with Records\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - SF (软件基础)\n  - PLF (编程语言基础)\n  - Coq\n  - 笔记\n---\n\n\n```coq\nInductive ty : Type :=\n  (* record types *)\n  | RNil : ty\n  | RCons : string → ty → ty → ty.\n```\n\nwe need typecon to identify record...\n\n\n```coq\nInductive tm : Type :=\n  | rproj ...?  isn't it as well?\n  (* record terms *)\n  | rnil : tm\n  | rcons : string → tm → tm → tm.\n``\n\nas a list...\n\n\nfor Record, can compiler reorder the fields? (SML and OCaml)\n\n\n\n\n\n"
  },
  {
    "path": "_posts/read_sf_plf/2019-03-15-sf-plf-15-norm-STLC.md",
    "content": "---\ntitle: \"「SF-PLF」15 Norm\"\nsubtitle: \"Programming Language Foundations - Normalization of STLC\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - SF (软件基础)\n  - PLF (编程语言基础)\n  - Coq\n  - 笔记\n---\n\nTBD\n"
  },
  {
    "path": "_posts/read_sf_plf/2019-03-16-sf-plf-16-lib-tactics.md",
    "content": "---\ntitle: \"「SF-PLF」16 LibTactics\"\nsubtitle: \"Programming Language Foundations - A Collection of Handy General-Purpose Tactics\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - SF (软件基础)\n  - PLF (编程语言基础)\n  - Coq\n  - 笔记\n---\n\nTBD\n"
  },
  {
    "path": "_posts/read_sf_plf/2019-03-17-sf-plf-17-use-tactics.md",
    "content": "---\ntitle: \"「SF-PLF」17 UseTactics\"\nsubtitle: \"Programming Language Foundations - Tactic Library For Coq\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - SF (软件基础)\n  - PLF (编程语言基础)\n  - Coq\n  - 笔记\n---\n\n```coq\nFrom PLF Require Import LibTactics.\n```\n\n`LibTactics`  vs. `SSReflect` (another tactics package)\n\n- for PL      vs. for math\n- traditional vs. rethinks..so harder\n\n\nTactics for Naming and Performing Inversion\n-------------------------------------------\n\n### `introv`\n\n```coq\nTheorem ceval_deterministic: ∀c st st1 st2,\n  st =[ c ]⇒ st1 →\n  st =[ c ]⇒ st2 →\n  st1 = st2.\nintros c st st1 st2 E1 E2. (* 以往如果想给 Hypo 命名必须说全 *)\nintrov E1 E2.              (* 现在可以忽略 forall 的部分 *)\n```\n\n### `inverts`\n\n```coq\n(* was... 需要 subst, clear *)\n- inversion H. subst. inversion H2. subst. \n(* now... *)\n- inverts H. inverts H2. \n\n\n(* 可以把 invert 出来的东西放在 goal 的位置让你自己用 intro 命名！*)\ninverts E2 as.\n```\n\n\n\n\n\n\n\nTactics for N-ary Connectives\n-----------------------------\n\n> Because Coq encodes conjunctions and disjunctions using binary constructors ∧ and ∨...\n> to work with a `N`-ary logical connectives...\n\n### `splits`\n\n> n-ary conjunction\n\nn-ary `split`\n\n\n### `branch`\n\n> n-ary disjunction\n\nfaster `destruct`?\n\n\n\n\n\n\nTactics for Working with Equality\n---------------------------------\n\n\n### `asserts_rewrite` and `cuts_rewrite`\n\n\n### `substs`\n\nbetter `subst` - not fail on circular eq\n\n\n### `fequals`\n\nvs `f_equal`?\n\n\n### `applys_eq`\n\nvariant of `eapply` \n\n\n\n\n\nSome Convenient Shorthands\n--------------------------\n\n\n### `unfolds`\n\nbetter `unfold`\n\n\n### `false` and `tryfalse`\n\nbetter `exfalso`\n\n\n### `gen` \n\nshorthand for `generalize dependent`, multiple arg.\n\n```coq\n(* old *)\nintros Gamma x U v t S Htypt Htypv.\ngeneralize dependent S. generalize dependent Gamma.\n \n(* new...so nice!!! *)\nintrov Htypt Htypv. gen S Gamma.\n```\n\n\n### `admits`, `admit_rewrite` and `admit_goal`\n\nwrappers around `admit`\n\n\n### `sort`\n\n> proof context more readable \n\nvars       -> top\nhypotheses -> bottom\n\n\n\n\n\n\n\nTactics for Advanced Lemma Instantiation\n----------------------------------------\n\n\n### Working on `lets` \n\n### Working on `applys`, `forwards` and `specializes`\n\n"
  },
  {
    "path": "_posts/read_sf_plf/2019-03-18-sf-plf-18-use-auto.md",
    "content": "---\ntitle: \"「SF-PLF」18 UseAuto\"\nsubtitle: \"Programming Language Foundations - Theory And Practice Of Automation In Coq Proofs\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - SF (软件基础)\n  - PLF (编程语言基础)\n  - Coq\n  - 笔记\n---\n\n\n\n## Basic Features of Proof Search\n\n### Strength of Proof Search\n\n> four proof-search tactics: `auto`, `eauto`, `iauto` and `jauto`.\n\n\n\n\n---\n\n\n## How Proof Search Works\n\n### Search Depth\n\n### Backtracking\n\n### Adding Hints\n\n### Integration of Automation in Tactics\n\n\n\n---\n\n\n\n## Example Proofs\n\n\n\n---\n\n\n\n## Advanced Topics in Proof Search\n\n\n###\n\n\n---\n\n\n## Decision Procedures\n\n\n### Omega\n\n### Ring\n\n### Congurence\n\n"
  },
  {
    "path": "_posts/read_sf_plf/2019-03-19-sf-plf-19-partial-eval.md",
    "content": "---\ntitle: \"「SF-PLF」19 PE\"\nsubtitle: \"Programming Language Foundations - Partial Evaluation\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - SF (软件基础)\n  - PLF (编程语言基础)\n  - Coq\n  - 笔记\n---\n\nTBD\n"
  },
  {
    "path": "_posts/read_sf_qc/2019-09-02-sf-qc-02-typeclasses.md",
    "content": "---\ntitle: \"「SF-QC」2 TypeClasses\"\nsubtitle: \"Quickcheck - A Tutorial on Typeclasses in Coq\"\nlayout: post\nauthor: \"Hux\"\nheader-style: text\nhidden: true\ntags:\n  - SF (软件基础)\n  - QC (Quickcheck)\n  - Coq\n  - 笔记\n---\n\nConsiderring printing different types with this common idiom:\n\n```coq\nshowBool : bool → string\nshowNat : nat → string\nshowList : {A : Type} (A → string) → (list A) → string\nshowPair : {A B : Type} (A → string) → (B → string) → A * B → string\n\nDefinition showListOfPairsOfNats := showList (showPair showNat showNat)   (* LOL *)\n```\n\n> The designers of Haskell addressed this clunkiness through _typeclasses_, a mechanism by which the typechecker is instructed to automatically construct \"type-driven\" functions [Wadler and Blott 1989].\n\nCoq followed Haskell's lead as well, but\n\n> because Coq's type system is so much richer than that of Haskell, and because typeclasses in Coq are used to automatically construct not only programs but also proofs, Coq's presentation of typeclasses is quite a bit less \"transparent\"\n\n\nBasics\n------\n\n### Classes and Instances\n\n```coq\nClass Show A : Type := {\n  show : A → string\n}.\n\nInstance showBool : Show bool := {\n  show := fun b:bool ⇒ if b then \"true\" else \"false\"\n}.\n```\n\nComparing with Haskell:\n\n```haskell\nclass Show a where\n  show :: a -> string\n\n--  you cannot override a `instance` so in reality you need a `newtype` wrapper to do this\ninstance Show Bool where\n  show b = if b then \"True\" else \"Fasle\"\n```\n\n> The show function is sometimes said to be overloaded, since it can be applied to arguments of many types, with potentially radically different behavior depending on the type of its argument.\n\n\nNext, we can define functions that use the overloaded function show like this:\n\n```coq\nDefinition showOne {A : Type} `{Show A} (a : A) : string :=\n  \"The value is \" ++ show a.\n  \nCompute (showOne true).\nCompute (showOne 42).\n\nDefinition showTwo {A B : Type}\n           `{Show A} `{Show B} (a : A) (b : B) : string :=\n  \"First is \" ++ show a ++ \" and second is \" ++ show b.\n\nCompute (showTwo true 42).\nCompute (showTwo Red Green).\n```\n\n> The parameter `` `{Show A}`` is a _class constraint_, which states that the function showOne is expected to be applied only to types A that belong to the Show class.\n\n> Concretely, this constraint should be thought of as an _extra parameter_ to showOne supplying _evidence_ that A is an instance of Show — i.e., it is essentially just a show function for A, which is implicitly invoked by the expression show a.\n\n读时猜测（后来发现接下来有更正确的解释）：`show` 在 name resolution 到 `class Show` 时就可以根据其参数的 type（比如 `T`）infer 出「我们需要一个 `Show T` 的实现（`instance`，其实就是个 table）」，在 Haskell/Rust 中这个 table 会在 lower 到 IR 时才 made explicit，而 Coq 这里的语法就已经强调了这里需要 implicitly-and-inferred `{}` 一个 table，这个 table 的名字其实不重要，只要其 type 是被 `A` parametrized 的 `Show` 就好了，类似 ML 的 `functor` 或者 Java 的 generic `interface`。\n\nThis is _Ad-hoc polymorphism_.\n\n\n#### Missing Constraint\n\nWhat if we forget the class constrints:\n\n```coq\nError:\nUnable to satisfy the following constraints:\nIn environment:\nA : Type\na : A\n\n?Show : \"Show A\"\n```\n\n\n#### Class `Eq`\n\n```coq\nClass Eq A :=\n  {\n    eqb: A → A → bool;\n  }.\n\nNotation \"x =? y\" := (eqb x y) (at level 70).\n\nInstance eqBool : Eq bool :=\n  {\n    eqb := fun (b c : bool) ⇒ \n       match b, c with\n         | true, true ⇒ true\n         | true, false ⇒ false\n         | false, true ⇒ false\n         | false, false ⇒ true\n       end\n  }.\n\nInstance eqNat : Eq nat :=\n  {\n    eqb := Nat.eqb\n  }.\n```\n\n> Why should we need to define a typeclass for boolean equality when _Coq's propositional equality_ (`x = y`) is completely generic? \n> while it makes sense to _claim_ that two values `x` and `y` are equal no matter what their type is, it is not possible to write a _decidable equality checker_ for arbitrary types. In particular, equality at types like `nat → nat` is undecidable.\n\n`x = y` 返回一个需要去证的 `Prop` (relational) 而非 executable `Fixpoint` (functional)  \n因为 function 的 equality 有时候会 undeciable，所以才需要加 Functional Extensionality `Axiom`（见 LF-06）\n\n```coq\nInstance eqBoolArrowBool: Eq (bool -> bool) :=\n  {\n    eqb := fun (f1 f2 : bool -> bool) =>\n      (f1 true) =? (f2 true) && (f1 false) =? (f2 false) \n  }.\n\nCompute (id =? id).      (* ==> true *)\nCompute (negb =? negb).  (* ==> true *)\nCompute (id =? negb).    (* ==> false *)\n```\n\n这里这个 `eqb` 的定义也是基于 extensionality 的定义，如果考虑到 effects（divergence、IO）是很容易 break 的（类似 parametricity）\n\n\n\n### Parameterized Instances: New Typeclasses from Old\n\nStructural recursion \n\n```coq\nInstance showPair {A B : Type} `{Show A} `{Show B} : Show (A * B) :=\n  {\n    show p :=\n      let (a,b) := p in \n        \"(\" ++ show a ++ \",\" ++ show b ++ \")\"\n  }.\nCompute (show (true,42)).\n```\n\nStructural equality\n\n```coq\nInstance eqPair {A B : Type} `{Eq A} `{Eq B} : Eq (A * B) :=\n  {\n    eqb p1 p2 :=\n      let (p1a,p1b) := p1 in\n      let (p2a,p2b) := p2 in\n      andb (p1a =? p2a) (p1b =? p2b)\n  }.\n```\n\nSlightly more complicated example: typical list:\n\n```coq\n(* the book didn't use any from ListNotation *)\nFixpoint showListAux {A : Type} (s : A → string) (l : list A) : string :=\n  match l with\n    | nil ⇒ \"\"\n    | cons h nil ⇒ s h\n    | cons h t ⇒ append (append (s h) \", \") (showListAux s t)\n  end.\nInstance showList {A : Type} `{Show A} : Show (list A) :=\n  {\n    show l := append \"[\" (append (showListAux show l) \"]\")\n  }.\n  \n(* I used them though *)\nFixpoint eqListAux {A : Type} `{Eq A} (l1 l2 : list A) : bool :=\n  match l1, l2 with\n    | nil, nil => true\n    | (h1::t1), (h2::t2) => (h1 =? h2) && (eqListAux t1 t2)\n    | _, _ => false\n  end.\n\nInstance eqList {A : Type} `{Eq A} : Eq (list A) :=\n  {\n    eqb l1 l2 := eqListAux l1 l2\n  }.\n```\n\n\n\n### Class Hierarchies\n\n> we might want a typeclass `Ord` for \"ordered types\" that support both equality and a less-or-equal comparison operator.\n\nA bad way would be declare a new class with two func `eq` and `le`.\n\nIt's better to establish dependencies between typeclasses, similar with OOP `class` inheritence and subtyping (but better!), this gave good code reuses.\n\n> We often want to organize typeclasses into hierarchies.\n\n```coq\nClass Ord A `{Eq A} : Type :=\n  {\n    le : A → A → bool\n  }.\nCheck Ord. (* ==>\nOrd\n     : forall A : Type, Eq A -> Type\n*)\n```\n\nclass `Eq` is a \"super(type)class\" of `Ord` (not to be confused with OOP superclass)\n\nThis is _Sub-typeclassing_.\n\n```coq\nFixpoint listOrdAux {A : Type} `{Ord A} (l1 l2 : list A) : bool := \n  match l1, l2 with\n  | [], _ => true\n  | _, [] => false\n  | h1::t1, h2::t2 => if (h1 =? h2)\n                     then (listOrdAux t1 t2)\n                     else (le h1 h2)\n  end.\n\nInstance listOrd {A : Type} `{Ord A} : Ord (list A) :=\n  {\n    le l1 l2 := listOrdAux l1 l2\n  }.\n\n(* truthy *)\nCompute (le [1] [2]).\nCompute (le [1;2] [2;2]).\nCompute (le [1;2;3] [2]).\n\n(* falsy *)\nCompute (le [1;2;3] [1]).\nCompute (le [2] [1;2;3]).\n```\n\n\n\nHow It works\n------------\n\n### Implicit Generalization\n\n所以 `` `{...}`` 这个 \"backtick\" notation is called _implicit generalization_，比 implicit `{}` 多做了一件自动 generalize 泛化 free varabile 的事情。\n\n> that was added to Coq to support typeclasses but that can also be used to good effect elsewhere.\n\n```coq\nDefinition showOne1 `{Show A} (a : A) : string :=\n  \"The value is \" ++ show a.\n\nPrint showOne1.\n(* ==>\n    showOne1 = \n      fun (A : Type) (H : Show A) (a : A) => \"The value is \" ++ show a\n           : forall A : Type, Show A -> A -> string\n\n    Arguments A, H are implicit and maximally inserted\n*)\n```\n\n> notice that the occurrence of `A` inside the `` `{...}`` is unbound and automatically insert the binding that we wrote explicitly before.\n\n> The \"implicit and maximally generalized\" annotation on the last line means that the automatically inserted bindings are treated (注：printed) as if they had been written with `{...}`, rather than `(...)`.\n\n> The \"implicit\" part means that the type argument `A` and the `Show` witness `H` are usually expected to be left implicit  \n> whenever we write `showOne1`, Coq will automatically insert two _unification variables_ as the first two arguments.\n\n> This automatic insertion can be disabled by writing `@`, so a bare occurrence of `showOne1` means the same as `@showOne1 _ _`\n\n这里的 witness `H` 即 `A` implements `Show` 的 evidence，本质就是个 table or record，可以 written more explicitly:\n\n```coq\nDefinition showOne2 `{_ : Show A} (a : A) : string :=\n  \"The value is \" ++ show a.\n\nDefinition showOne3 `{H : Show A} (a : A) : string :=\n  \"The value is \" ++ show a.\n```\n\n甚至 \n\n```coq\nDefinition showOne4 `{Show} a : string :=\n  \"The value is \" ++ show a.\n```\n\n```coq\nshowOne = \nfun (A : Type) (H : Show A) (a : A) => \"The value is \" ++ show a\n     : forall A : Type, Show A -> A -> string\n\nSet Printing Implicit.\n\nshowOne = \nfun (A : Type) (H : Show A) (a : A) => \"The value is \" ++ @show A H a     (* <-- 注意这里 *)\n     : forall A : Type, Show A -> A -> string\n```\n\n#### vs. Haskell\n\n顺便，Haskell 的话，`Show` 是可以直接 inferred from the use of `show` 得\n\n```haskell\nPrelude> showOne a = show a\nPrelude> :t showOne\nshowOne :: Show a => a -> String\n```\n\n但是 Coq 不行，会退化上「上一个定义的 instance Show」，还挺奇怪的（\n\n```coq\nDefinition showOne5 a : string :=  (* not generalized *)\n  \"The value is \" ++ show a.\n```\n\n#### Free Superclass Instance\n\n``{Ord A}` led Coq to fill in both `A` and `H : Eq A` because it's the superclass of `Ord` (appears as the second argument). \n\n```coq\nDefinition max1 `{Ord A} (x y : A) :=\n  if le x y then y else x.\n\nSet Printing Implicit.\nPrint max1.\n(* ==>\n     max1 = \n       fun (A : Type) (H : Eq A) (H0 : @Ord A H) (x y : A) =>\n         if @le A H H0 x y then y else x\n\n   : forall (A : Type) (H : Eq A), \n       @Ord A H -> A -> A -> A    \n*)\nCheck Ord.\n(* ==> Ord : forall A : Type, Eq A -> Type *)\n```\n\n`Ord` type 写详细的话可以是：\n\n```coq\nOrd : forall (A : Type), (H: Eq A) -> Type\n```\n\n\n#### Other usages of `` `{} ``\n\nImplicit generalized `Prop` mentioning free vars.\n\n```coq\nGeneralizable Variables x y.\n\nLemma commutativity_property : `{x + y = y + x}.\nProof. intros. omega. Qed.\n\nCheck commutativity_property.\n```\n\nImplicit generalized `fun`/`λ`, however...\n\n```coq\nDefinition implicit_fun := `{x + y}.\nCompute (implicit_fun 2 3)  (* ==> Error *)\nCompute (@implicit_fun 2 3)\n```\n\nImplicitly-generalized but inserted as explicit via `` `(...)``\n\n```coq\nDefinition implicit_fun := `(x + y).\nCompute (implicit_fun 2 3)\n```\n\n这里可以看到 Coq 的所有语法都是正交的（非常牛逼……）\n- `()`/`{}` 控制是否是 implicit argument\n- `` ` ``-prefix 控制是否做 implicit generalization\n  - N.B. 可能你忘记了但是 `→` is degenerated `∀` (`Π`)，所以 generalization 自然会生成 `fun`\n\n\n### Records are Products\n\n> Record types must be declared before they are used. For example:\n\n```coq\nRecord Point :=\n  Build_Point\n    {\n      px : nat;\n      py : nat\n    }.\n\n(* built with constructor *)\nCheck (Build_Point 2 4).\n\n(* built with record syntax *)\nCheck {| px := 2; py := 4 |}.\nCheck {| py := 2; px := 4 |}.\n\n(* field access, with a clunky \"dot notation\" *)\nDefinition r : Point := {| px := 2; py := 4 |}.\nCompute (r.(px) + r.(py)).\n```\n\n和 OCaml 一样是 nominal typing 而非 structural typing。\n类似于 OCaml 中的 record 其实到 backend 了就会和 tuple 等价：都会 lower 到 Heap Block），\nCoq 中的 Record 其实和 Pair/Product 也是等价：都是 arity 为 2 的 Inductive type：\n\n```coq\nInductive Point : Set := \n  | Build_Point : nat → nat → Point.\n```\n\n我仿造 `Print px.` 输出的定义模拟了一下：\n\n```coq\nInductive Point2 : Set := \n  | Build_Point2 (px2:nat) (py2:nat).\nDefinition px2 := fun p : Point2 => let (px, _) := p in px.\nDefinition py2 := fun p : Point2 => let (_, py) := p in py.\n\nDefinition r2 : Point2 := Build_Point2 2 4.\nCompute (r2.(px2) + r2.(py2)).                        (* => 6 *)\n\nDefinition r2 : Point2 := {| px2 := 2; py2 := 4 |}.   (* Error: px2 is not a projection *)\n```\n\n可以发现 dot notation 是可以工作的，`.` 应该只是一个 pipe\n但是 `{|...|}` 不知道为什么这里会认为 `px2` 不是一个 record projection.\n\n\n> Note that the field names have to be different. Any given field name can belong to only one record type. \n> This greatly simplifies type inference!\n\n\n### Typeclasses are Records\n\n> Typeclasses and instances, in turn, are basically just syntactic sugar for record types and values (together with a bit of magic for using proof search to fill in appropriate instances during typechecking...\n\n> Internally, a typeclass declaration is elaborated into a _parameterized_ `Record` declaration:\n\n```coq\nClass Show A : Type := { show : A → string }.\n\nPrint Show.\nRecord Show (A : Type) : Type := \n    Build_Show { show : A -> string }\n\nSet Printing All.\nPrint Show.\nVariant Show (A : Type) : Type :=\n    Build_Show : forall _ : forall _ : A, string, Show A\n\n(* to make it more clear... *)\nInductive Show (A : Type) : Type :=\n  | Build_Show : ∀(show : ∀(a : A), string), Show A\n  \n(* or more GADT looking, i.e., implicit generalized *)\nInductive Show (A : Type) : Type :=\n  | Build_Show : (A -> string) -> Show A\n```\n\nCoq actually call a single-field record `Variant`. \nWell actually, I found it's for any single-constructor `Inductive`ly constructed type. \nYou can even use `Variant` nonchangbly with `Inductive` as a keyword...\n\n```coq\nSet Printing All.\nPrint Point.\nVariant Point : Set :=\n    Build_Point : forall (_ : nat) (_ : nat), Point\n```\n\n> Analogously, Instance declarations become record values:\n\n```coq\nPrint showNat.\nshowNat = {| show := string_of_nat |}\n    : Show nat\n```\n\n> Similarly, overloaded functions like show are really just _record projections_, which in turn are just functions that select a particular argument of a one-constructor Inductive type.\n\n```coq\nPrint show.\nshow = \n  fun (A : Type) (Show0 : Show A) => \n    let (show) := Show0 in show\n      : forall A : Type, Show A -> A -> string\n\nSet Printing All.\nPrint show.\nshow = \n  fun (A : Type) (Show0 : Show A) =>\n    match Show0 return (forall _ : A, string) with\n    | Build_Show _ show => show\n    end\n      : forall (A : Type) (_ : Show A) (_ : A), string\n```\n\n\n### Inferring Instances\n\n> appropriate instances are automatically inferred (and/or constructed!) during typechecking.\n\n```coq\nDefinition eg42 := show 42.\n\nSet Printing Implicit.\nPrint eg42.\neg42 = @show nat showNat 42 : string\n```\n\ndifferent with `Compute`, `Print` 居然还可以这么把所有 implicit argument (after inferred) 都给 print 出来……\n\ntype inferrence: \n\n- `show` is expanded to `@show _ _ 42`\n- obviously it's `@show nat __42`\n- obviously it's `@show nat (?H : Show Nat) 42`\n\nOkay now where to find this witness/evidence/instance/record/table/you-name-it `?H` \n\n> It attempts to find or construct such a value using a _variant of the `eauto` proof search_ procedure that refers to a \"hint database\" called `typeclass_instances`.\n\n```coq\nPrint HintDb typeclass_instances.  (* too much to be useful *)\n```\n\n\"hint database\" to me is better understood as a reverse of environment or typing context `Γ`. Though specialized with only `Instance` there.\n（这么一看实现一个 Scala 的 `Implicit` 也不难啊）\n\nCoq can even print what's happening during this proof search!\n\n```coq\nSet Typeclasses Debug.\nCheck (show 42).\n(* ==>\n     Debug: 1: looking for (Show nat) without backtracking\n     Debug: 1.1: exact showNat on (Show nat), 0 subgoal(s)\n*)\n\nCheck (show (true,42)).\n(* ==>\n     Debug: 1: looking for (Show (bool * nat)) without backtracking\n     Debug: 1.1: simple apply @showPair on (Show (bool * nat)), 2 subgoal(s)\n     Debug: 1.1.3 : (Show bool)\n     Debug: 1.1.3: looking for (Show bool) without backtracking\n     Debug: 1.1.3.1: exact showBool on (Show bool), 0 subgoal(s)\n     Debug: 1.1.3 : (Show nat)\n     Debug: 1.1.3: looking for (Show nat) without backtracking\n     Debug: 1.1.3.1: exact showNat on (Show nat), 0 subgoal(s)      *)\nUnset Typeclasses Debug.\n```\n\n> In summary, here are the steps again:\n\n```coq\nshow 42\n    ===>   { Implicit arguments }\n@show _ _ 42\n    ===>   { Typing }\n@show (?A : Type) (?Show0 : Show ?A) 42\n    ===>   { Unification }\n@show nat (?Show0 : Show nat) 42\n    ===>   { Proof search for Show Nat returns showNat }\n@show nat showNat 42\n```\n\n\nTypeclasses and Proofs\n----------------------\n\n### Propositional Typeclass Members\n\n```coq\nClass EqDec (A : Type) {H : Eq A} := \n  { \n    eqb_eq : ∀ x y, x =? y = true ↔ x = y \n  }.\n```\n\n```coq\nInstance eqdecNat : EqDec nat := \n  {\n    eqb_eq := Nat.eqb_eq\n  }.\n```\n\n这里可以用于抽象 LF-07 的 reflection\n\n\n### Substructures\n\n> Naturally, it is also possible to have typeclass instances as members of other typeclasses: these are called _substructures_. \n\n这里的 `relation` 来自 Prelude 不过和 LF-11 用法一样：\n\n```coq\nRequire Import Coq.Relations.Relation_Definitions.\nClass Reflexive (A : Type) (R : relation A) :=\n  { \n    reflexivity : ∀ x, R x x\n  }.\nClass Transitive (A : Type) (R : relation A) :=\n  {\n    transitivity : ∀ x y z, R x y → R y z → R x z\n  }.\n```\n\n```coq\nClass PreOrder (A : Type) (R : relation A) :=\n  { PreOrder_Reflexive :> Reflexive A R ;\n    PreOrder_Transitive :> Transitive A R }.\n```\n\n> The syntax `:>` indicates that each `PreOrder` can be seen as a `Reflexive` and `Transitive` relation, so that, any time a reflexive relation is needed, a preorder can be used instead.\n\n这里的 `:>` 方向和 subtyping 的 _subsumption_ 是反着的……跟 SML 的 ascription `:>` 一样……\n\n- subtyping  `T :> S` : value of `S` can safely be used as value of `T`\n- ascription `P :> R` : value of `P` can safely be used as value of `R`\n\nWhy?\n\n\n\nSome Useful Typeclasses\n-----------------------\n\n### `Dec`\n\n> The `ssreflect` library defines what it means for a proposition `P` to be _decidable_ like this...\n\n```coq\nRequire Import ssreflect ssrbool.\nPrint decidable.\n(* ==>\n     decidable = fun P : Prop => {P} + {~ P}\n*)\n```\n\n> .. where `{P} + {¬ P}` is an \"informative disjunction\" of `P` and `¬P`.\n\n即两个 evidence（参考 LF-07)\n\n```coq\nClass Dec (P : Prop) : Type :=\n  {\n    dec : decidable P\n  }.\n```\n\n### Monad\n\n> In Haskell, one place typeclasses are used very heavily is with the Monad typeclass, especially in conjunction with Haskell's \"do notation\" for monadic actions.\n\n> Monads are an extremely powerful tool for organizing and streamlining code in a wide range of situations where computations can be thought of as yielding a result along with some kind of \"effect.\"\n\n说话很严谨「in a wide range of situations where ... \"effect\"」\n\n> most older projects simply define their own monads and monadic notations — sometimes typeclass-based, often not — while newer projects use one of several generic libraries for monads. Our current favorite (as of Summer 2017) is the monad typeclasses in Gregory Malecha's `ext-lib` package:\n\n<https://github.com/coq-ext-lib/coq-ext-lib/blob/v8.5/theories/Structures/Monad.v>\n\n```coq\nRequire Export ExtLib.Structures.Monads.\nExport MonadNotation.\nOpen Scope monad_scope.\n```\n\n```coq\nClass Monad (M : Type → Type) : Type := { \n  ret : ∀ {T : Type}, T → M T ;\n  bind : ∀ {T U : Type}, M T → (T → M U) → M U\n}.\n\nInstance optionMonad : Monad option := {\n  ret T x := Some x ;\n  bind T U m f :=\n    match m with\n      None ⇒ None\n    | Some x ⇒ f x\n    end\n}.\n```\n\nCompare with Haskell:\n\n```haskell\nclass Applicative m => Monad (m :: * -> *) where\n  return :: a -> m a\n  (>>=) :: m a -> (a -> m b) -> m b\n  \ninstance Monad Maybe where\n  return = Just\n  (>>=)  = (>>=)\n  where\n    (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b\n    Nothing  >>= _ = Nothing\n    (Just x) >>= f = f x\n```\n\nAfter mimic `do` notation: (as PLF-11)\n\n```coq\nDefinition sum3 (l : list nat) : option nat :=\n  x0 <- nth_opt 0 l ;;\n  x1 <- nth_opt 1 l ;;\n  x2 <- nth_opt 2 l ;;\n  ret (x0 + x1 + x2).\n```\n\n\nControlling Instantiation\n-------------------------\n\n### \"Defaulting\"\n\nWould better explicitly typed. searching can be stupid\n\n### Manipulating the Hint Database\n\n> One of the ways in which Coq's typeclasses differ most from Haskell's is the lack, in Coq, of an automatic check for \"overlapping instances.\"\n\n在 Haskell 中一大 use case 是可以做类似 C++ 的 partial specification（偏特化）\n\n- Check out [this](https://kseo.github.io/posts/2017-02-05-avoid-overlapping-instances-with-closed-type-families.html) on the pros and cons of overlapping instances in Haskell\n- Check out [this] (https://www.ibm.com/developerworks/community/blogs/12bb75c9-dfec-42f5-8b55-b669cc56ad76/entry/c__e6_a8_a1_e6_9d_bf__e7_a9_b6_e7_ab_9f_e4_bb_80_e4_b9_88_e6_98_af_e7_89_b9_e5_8c_96?lang=en) on template partial specification in C++\n\n> That is, it is completely legal to define a given type to be an instance of a given class in two different ways.\n> When this happens, it is unpredictable which instance will be found first by the instance search process;\n\nWorkarounds in Coq when this happen:\n1. removing instances from hint database\n2. priorities\n\n\n\nDebugging\n---------\n\nTBD.\n\n- Instantiation Failures\n- Nontermination\n\n\nAlternative Structuring Mechanisms\n----------------------------------\n\n_large-scale structuring mechanisms_\n\n> Typeclasses are just one of several mechanisms that can be used in Coq for structuring large developments. Others include:\n>\n> - canonical structures\n> - bare dependent records\n> - modules and functors\n\nModule and functors is very familiar!\n\n\nFurther Reading\n----------------------------------\n\nOn the origins of typeclasses in Haskell:\n\n- How to make ad-hoc polymorphism less ad hoc Philip Wadler and Stephen Blott. 16'th Symposium on Principles of Programming Languages, ACM Press, Austin, Texas, January 1989.\n  <http://homepages.inf.ed.ac.uk/wadler/topics/type-classes.html>  \n\nThe original paper on typeclasses In Coq:\n\n- Matthieu Sozeau and Nicolas Oury. First-Class Type Classes. TPHOLs 2008.\n  <https://link.springer.com/chapter/10.1007%2F978-3-540-71067-7_23>\n  \n"
  },
  {
    "path": "about.html",
    "content": "---\nlayout: page\ntitle: \"About\"\ndescription: \"「他单纯只想把日子过得不浪费」\"\nheader-img: \"img/bg-me-2022.jpg\"\nheader-mask: 0.3\nmultilingual: true\n---\n\n{% include multilingual-sel.html %}\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": "ads.txt",
    "content": "google.com, pub-6487568398225121, DIRECT, f08c47fec0942fa0"
  },
  {
    "path": "archive.html",
    "content": "---\ntitle: Archive\nlayout: default\ndescription: \"「我干了什么 究竟拿了时间换了什么」\"\nheader-img: \"img/bg-little-universe.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 }}\" class=\"tag-button\" title=\"{{ tag[0] }}\"\n\t\t\t\t\trel=\"{{ 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>__SEPERATOR__\n\t\t\t\t{% endfor %}\n\t\t\t\t{% endcapture %}\n\t\t\t\t{{ tags | split:'__SEPERATOR__' | sort }}\n\t\t\t</div>\n\n\t\t\t<!-- Article List -->\n\t\t\t<div class=\"mini-post-list js-result d-none\">\n\t\t\t\t{%- assign _sorted_list = site.posts -%}\n\t\t\t\t{%- assign _sorted_list = _sorted_list | sort: 'date' -%}\n\t\t\t\t{%- assign _sorted_list = _sorted_list | reverse -%}\n\n\n\t\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{%- assign _tag_encode = _tag | strip | url_encode -%}\n\t\t\t\t{%- if forloop.last -%}\n\t\t\t\t{%- assign _tags = _tags | append: _tag_encode -%}\n\t\t\t\t{%- else -%}\n\t\t\t\t{%- assign _tags = _tags | append: _tag_encode | append: ',' -%}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- endfor -%}\n\n\t\t\t\t{% comment %} group by year {% endcomment %}\n\t\t\t\t{%- assign _currentdate = _article.date | date: '%Y' -%}\n\t\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\t<span class=\"fa listing-seperator\">\n\t\t\t\t\t\t<span class=\"tag-text\">{{ _currentdate }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t\t{%- assign _date = _currentdate -%}\n\t\t\t\t\t{%- endif -%}\n\n\t\t\t\t\t<div class=\"post-preview item\" data-tags=\"{{ _tags }}\">\n\t\t\t\t\t\t<a href=\"{{ _article.url | prepend: site.baseurl }}\">\n\t\t\t\t\t\t\t<h2 class=\"post-title\">\n\t\t\t\t\t\t\t\t{{ _article.title }}\n\t\t\t\t\t\t\t</h2>\n\t\t\t\t\t\t\t{% if _article.subtitle %}\n\t\t\t\t\t\t\t<h3 class=\"post-subtitle\">\n\t\t\t\t\t\t\t\t{{ _article.subtitle }}\n\t\t\t\t\t\t\t</h3>\n\t\t\t\t\t\t\t{% endif %}\n\t\t\t\t\t\t</a>\n\t\t\t\t\t\t<hr>\n\t\t\t\t\t</div>\n\t\t\t\t\t{%- if forloop.last -%}\n\t\t\t\t</section>{%- endif -%}\n\t\t\t\t{% endfor %}\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</div>\n"
  },
  {
    "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": "@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: #a3a3a3;\n  padding-bottom: 1em;\n}\n.sidebar-container h5 a {\n  color: #a3a3a3 !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  margin-left: 0;\n  font-size: 13px;\n  font-weight: bold;\n}\n.side-catalog .catalog-body .h3_nav {\n  margin-left: 6px;\n  font-size: 13px;\n  font-weight: bold;\n}\n.side-catalog .catalog-body .h4_nav {\n  margin-left: 12px;\n  font-size: 12px;\n}\n.side-catalog .catalog-body .h4_nav a {\n  max-width: 170px;\n}\n.side-catalog .catalog-body .h5_nav .h6_nav {\n  margin-left: 18px;\n  font-size: 12px;\n}\n.side-catalog .catalog-body .h5_nav .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: #a3a3a3;\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: #a3a3a3;\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.language-coq .highlight .err {\n  background-color: transparent;\n}\n.highlight .k,\n.language-coq .highlight .err {\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-table .rouge-gutter,\n.highlighter-rouge .highlight .rouge-table .rouge-code {\n  border: 0 !important;\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: 36px;\n  }\n  .highlighter-rouge .highlight table > tbody > tr > td > pre {\n    padding: 14px 10px;\n  }\n}\n.search-page {\n  position: fixed;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  z-index: 100;\n  background: #fff;\n  -webkit-transition: all 400ms cubic-bezier(0.32, 1, 0.23, 1);\n  transition: all 400ms cubic-bezier(0.32, 1, 0.23, 1);\n  -webkit-transform: translate(0, 100%);\n  -ms-transform: translate(0, 100%);\n  transform: translate(0, 100%);\n  opacity: 0;\n}\n.search-page.search-active {\n  opacity: 1;\n  -webkit-transform: translate(0, 0) scale(1, 1);\n  -ms-transform: translate(0, 0) scale(1, 1);\n  transform: translate(0, 0) scale(1, 1);\n}\n.search-page.search-active .search-main {\n  opacity: 1;\n}\n.search-page .search-main {\n  padding-top: 80px;\n  height: 100%;\n  opacity: 0;\n  -webkit-transition: all 400ms cubic-bezier(0.32, 1, 0.23, 1) 250ms;\n  transition: all 400ms cubic-bezier(0.32, 1, 0.23, 1) 250ms;\n}\n.search-page .search-main .row,\n.search-page .search-main .row > div {\n  height: 100%;\n}\n.search-page .search-icon-close-container {\n  position: absolute;\n  z-index: 1;\n  padding: 16px;\n  top: 0;\n  right: 2px;\n}\n.search-page .search-icon-close-container i {\n  font-size: 20px;\n}\n.search-page #search-input {\n  font-family: \"Fira Code\", Menlo, Monaco, Consolas, \"Courier New\", monospace;\n  border: none;\n  outline: none;\n  padding: 0;\n  margin: 0;\n  width: 100%;\n  font-size: 30px;\n  font-weight: bold;\n  color: #404040;\n}\n@media only screen and (min-width: 768px) {\n  .search-page #search-input {\n    margin-left: 20px;\n  }\n}\n.search-page #search-results {\n  overflow: auto;\n  height: 100%;\n  -webkit-overflow-scrolling: touch;\n  padding-bottom: 80px;\n}\n.search-icon a,\n.search-icon-close {\n  cursor: pointer;\n  font-size: 30px;\n  color: #311e3e;\n  -webkit-transition: all 0.25s;\n  transition: all 0.25s;\n}\n.search-icon a:hover,\n.search-icon-close:hover {\n  opacity: 0.8;\n}\n.search-icon,\n.search-icon-close {\n  font-size: 16px;\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}\n@media screen and (max-width: 768px) {\n  p {\n    margin: 25px 0;\n  }\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 {\n  margin-top: 10px;\n}\ntable.table > tbody th,\ntable.table > thead th,\ntable.table > tbody td,\ntable.table > thead td {\n  border: 1px solid #eee !important;\n}\n@media screen and (max-width: 767px) {\n  .table-responsive {\n    border: 0;\n  }\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: 50px 0 20px;\n  line-height: 1.4;\n}\n.post-container h1 + p,\n.post-container h2 + p,\n.post-container h3 + p,\n.post-container h4 + p,\n.post-container h5 + p,\n.post-container h6 + p {\n  margin-top: 20px;\n}\n.post-container h2::before {\n  content: \" \";\n  display: block;\n  border-bottom: 1px solid #ececec;\n  margin-top: 44px;\n  margin-bottom: 30px;\n}\n.post-container h4 {\n  font-size: 22px;\n  font-weight: 600;\n  color: gray;\n}\n.post-container h4 + p {\n  margin-top: 10px;\n}\n.post-container h5,\n.post-container h6 {\n  font-size: 20px;\n  font-weight: 600;\n  color: gray;\n}\n.post-container h5 + p,\n.post-container h6 + p {\n  margin-top: 10px;\n}\n@media screen and (max-width: 768px) {\n  .post-container h1 {\n    font-size: 30px;\n  }\n  .post-container h2 {\n    font-size: 22px;\n  }\n  .post-container h3 {\n    font-size: 20px;\n  }\n  .post-container h4 {\n    font-size: 19px;\n  }\n  .post-container h5,\n  .post-container h6 {\n    font-size: 18px;\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: #a3a3a3;\n  font-weight: normal;\n  letter-spacing: 0.5px;\n}\n.post-container .anchorjs-link {\n  position: absolute;\n  text-decoration: none !important;\n}\n@media only screen and (max-width: 767px) {\n  /**\n   * Layout\n   * Since V1.6 we use absolute positioning to prevent to expand container-fluid\n   * which would cover tags. A absolute positioning make a new layer.\n   */\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   * Animation\n   * HuxBlog-Navbar using genuine Material Design Animation\n   */\n  #huxblog_navbar {\n    /**\n     * Sharable code and 'out' function\n     */\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     *'In' Animation\n     */\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: #a3a3a3;\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: 80px;\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: #a3a3a3;\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.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: #a3a3a3;\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.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.no-scroll {\n  overflow-y: hidden;\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\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.language-coq .highlight .err {\n  // Hux: a temp workaround to fix for Coq math symbol\n  // @see https://github.com/rouge-ruby/rouge/issues/1312\n  &:extend(.highlight .k);\n  background-color: transparent;\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-table {\n    .rouge-gutter,\n    .rouge-code {\n      border: 0 !important;\n    }\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: 36px;\n    }\n    table > tbody > tr > td > pre {\n      padding: 14px 10px;\n    }\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@import \"search.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  @media screen and (max-width: 768px) {\n    & {\n      margin: 25px 0;\n    }\n  }\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 {\n  margin-top: 10px;\n}\ntable.table > tbody,\ntable.table > thead {\n  th,\n  td {\n    border: 1px solid #eee !important;\n  }\n}\n@media screen and (max-width: 767px) {\n  .table-responsive {\n    border: 0;\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  .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: 50px 0 20px;\n    line-height: 1.4;\n    & + p {\n      margin-top: 20px;\n    }\n  }\n  h2::before {\n    content: \" \";\n    display: block;\n    border-bottom: 1px solid #ececec;\n    margin-top: 44px;\n    margin-bottom: 30px;\n  }\n  h4 {\n    font-size: 22px;\n    font-weight: 600;\n    color: gray;\n    & + p {\n      margin-top: 10px;\n    }\n  }\n  h5,\n  h6 {\n    font-size: 20px;\n    font-weight: 600;\n    color: gray;\n    & + p {\n      margin-top: 10px;\n    }\n  }\n  // downscale the font a little bit in mobile\n  @media screen and (max-width: 768px) {\n    h1 {\n      font-size: 30px;\n    }\n    h2 {\n      font-size: 22px;\n    }\n    h3 {\n      font-size: 20px;\n    }\n    h4 {\n      font-size: 19px;\n    }\n    h5,\n    h6 {\n      font-size: 18px;\n    }\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: @brand-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    text-decoration: none !important;\n  }\n}\n\n// Navigation\n\n// materialize, mobile only\n@media only screen and (max-width: 767px) {\n  /**\n   * Layout\n   * Since V1.6 we use absolute positioning to prevent to expand container-fluid\n   * which would cover tags. A absolute positioning make a new layer.\n   */\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   * Animation\n   * HuxBlog-Navbar using genuine Material Design Animation\n   */\n  #huxblog_navbar {\n    /**\n     * Sharable code and 'out' function\n     */\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     *'In' Animation\n     */\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,\n      .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: @brand-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: @brand-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: @brand-gray;\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// 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: @brand-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,\n    .tag {\n      padding: 0 10px 0 12px;\n    }\n  }\n  a,\n  .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, 0.117647) 0 1px 6px,\n        rgba(0, 0, 0, 0.239216) 0 1px 4px;\n    }\n    &.tag-button--all {\n      font-weight: 700;\n      color: #0085a1 !important;\n      &:hover,\n      &:active,\n      &.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/* 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 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}\n\n.no-scroll {\n  overflow-y: hidden;\n}\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.monospace () {\n    font-family: \"Fira Code\", Menlo, Monaco, Consolas, \"Courier New\", monospace;\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/search.less",
    "content": ".search-page {\n  position: fixed;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  z-index: 100;\n  background: #fff;\n  -webkit-transition: all 400ms cubic-bezier(0.32, 1, 0.23, 1);\n  transition: all 400ms cubic-bezier(0.32, 1, 0.23, 1);\n  -webkit-transform: translate(0, 100%);\n  -ms-transform: translate(0, 100%);\n  transform: translate(0, 100%);\n  opacity: 0;\n\n  &.search-active {\n    opacity: 1;\n    -webkit-transform: translate(0, 0) scale(1, 1);\n    -ms-transform: translate(0, 0) scale(1, 1);\n    transform: translate(0, 0) scale(1, 1);\n    .search-main {\n      opacity: 1;\n    }\n  }\n\n  .search-main {\n    padding-top: 80px;\n    height: 100%;\n    opacity: 0;\n    -webkit-transition: all 400ms cubic-bezier(0.32, 1, 0.23, 1) 250ms;\n    transition: all 400ms cubic-bezier(0.32, 1, 0.23, 1) 250ms;\n    .row,\n    .row > div {\n      height: 100%;\n    }\n  }\n\n  .search-icon-close-container {\n    position: absolute;\n    z-index: 1;\n    padding: 16px;\n    top: 0;\n    right: 2px;\n    i {\n      font-size: 20px;\n    }\n  }\n\n  #search-input {\n    .monospace;\n\n    // poorman's reset\n    border: none;\n    outline: none;\n    padding: 0;\n    margin: 0;\n    // poorman's reset end\n\n    width: 100%;\n    font-size: 30px;\n    font-weight: bold;\n    color: @gray-dark;\n   \n    @media only screen and (min-width: 768px) {\n      margin-left: 20px;\n    }\n  }\n\n  #search-results {\n    overflow: auto;\n    height: 100%;\n    -webkit-overflow-scrolling: touch;\n    padding-bottom: 80px;\n  }\n}\n\n.search-icon a,\n.search-icon-close {\n  cursor: pointer;\n  font-size: 30px;\n  color: #311e3e;\n  -webkit-transition: all 0.25s;\n  transition: all 0.25s;\n}\n\n.search-icon a:hover,\n.search-icon-close:hover {\n  opacity: 0.8;\n}\n\n.search-icon,\n.search-icon-close {\n  font-size: 16px;\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,\n    .h2_nav {\n      margin-left: 0;\n      font-size: 13px;\n      font-weight: bold;\n    }\n    .h3_nav {\n      margin-left: 6px;\n      font-size: 13px;\n      font-weight: bold;\n    }\n    .h4_nav {\n      margin-left: 12px;\n      font-size: 12px;\n      a {\n        max-width: 170px;\n      }\n    }\n    .h5_nav .h6_nav {\n      margin-left: 18px;\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}\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,\n  .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-light;\n  font-size: 14px;\n  h5 {\n    color: @brand-gray;\n    padding-bottom: 1em;\n    a {\n      color: @brand-gray !important;\n      text-decoration: none;\n    }\n  }\n  a {\n    color: @gray-light !important;\n    &:hover,\n    &:active {\n      color: @brand-primary !important;\n    }\n  }\n  .tags {\n    a {\n      border-color: @gray-light;\n      &:hover,\n      &: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,\n    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@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@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/* 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: @brand-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,\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: @black;\n  text-transform: uppercase;\n}\n\n.paper-button.colored,\n.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: @brand-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/** Card **/\n.card {\n  background: white;\n  width: 300px;\n  height: 300px;\n  position: relative;\n  margin: 16px;\n  border-radius: 2px;\n}\n"
  },
  {
    "path": "less/variables.less",
    "content": "// Variables\n\n@brand-primary: #0085a1;\n@brand-gray: #a3a3a3;\n\n@gray-dark: lighten(black, 25%);\n@gray-light: lighten(black, 75%);\n\n@white-faded: fade(white, 80%);\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.8.2\",\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\": \">=1.3.0\",\n        \"grunt-banner\": \"~0.2.3\",\n        \"grunt-contrib-less\": \"^2.0.0\",\n        \"grunt-contrib-uglify\": \"^4.0.1\",\n        \"grunt-contrib-watch\": \"^1.1.0\"\n    },\n    \"scripts\": {\n        \"start\": \"bundle exec jekyll serve\",\n        \"dev\": \"grunt watch & npm run start\",\n        \"boil\": \"git push boilerplate boilerplate:master\",\n        \"push\": \"git push origin master --tag\"\n    }\n}\n"
  },
  {
    "path": "pwa/manifest.json",
    "content": "{\n  \"name\": \"Hux Blog\",\n  \"short_name\": \"Hux Blog\",\n  \"description\": \"About an engineer & designer who loves web.\",\n  \"icons\": [{\n    \"src\": \"icons/128.png\",\n    \"sizes\": \"128x128\",\n    \"type\": \"image/png\"\n  }, {\n    \"src\": \"icons/512.png\",\n    \"sizes\": \"512x512\",\n    \"type\": \"image/png\"\n  }],\n  \"background_color\": \"#fff\",\n  \"theme_color\": \"#000\",\n  \"start_url\": \"/\",\n  \"display\": \"standalone\",\n  \"orientation\": \"portrait\"\n}\n"
  },
  {
    "path": "search.json",
    "content": "---\nlayout: null\n---\n[\n  {% for post in site.posts %}\n    {\n      \"title\"    : \"{{ post.title | escape }}\",\n      \"subtitle\" : \"{{ post.subtitle | escape }}\",\n      \"tags\"     : \"{{ post.tags | join: ', ' }}\",\n      \"url\"      : \"{{ site.baseurl }}{{ post.url }}\",\n      \"date\"     : \"{{ post.date }}\"\n    } {% unless forloop.last %},{% endunless %}\n  {% endfor %}\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}"
  }
]