├── .editorconfig ├── .eslintrc.js ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── LICENSE ├── ReadMe.md ├── ReadMe.zh.md ├── index.js ├── lib ├── hbe.blink.html ├── hbe.default.html ├── hbe.flip.html ├── hbe.js ├── hbe.shrink.html ├── hbe.style.css ├── hbe.surge.html ├── hbe.up.html ├── hbe.wave.html └── hbe.xray.html └── package.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | end_of_line = lf 10 | # editorconfig-tools is unable to ignore longs strings or urls 11 | max_line_length = null 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'env': { 3 | 'browser': true, 4 | 'commonjs': true, 5 | 'es6': true 6 | }, 7 | 'extends': 'eslint:recommended', 8 | 'parserOptions': { 9 | 'ecmaVersion': 10, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute to This Project 2 | 3 | ## **Did You Find a Bug?** 4 | 5 | - **Ensure the bug was not already reported** by searching on GitHub under [Issues]. 6 | - If you're unable to find an open issue addressing the problem, [open a new one]. Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring. 7 | 8 | ## **Did You Write a Patch That Fixes a Bug?** 9 | 10 | - Open a new GitHub pull request with the patch. 11 | 1. Fork this project 12 | 2. Create your feature branch: `git checkout -b my-new-feature` 13 | 3. Commit your changes: `git commit -am 'Add some feature'` 14 | 4. Push to the branch: `git push origin my-new-feature` 15 | 5. Submit a pull request :tada: 16 | - Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. 17 | 18 | ## **Do You Intend to Add a New Feature or Change an Existing One?** 19 | 20 | - Suggest your change as a new [issue] using the label `enhancement` **BEFORE** you start writing code. 21 | 22 | Thanks for contributing! :heart: 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Issue 2 | 3 | ## Expected Behavior 4 | 5 | ## Actual Behavior 6 | 7 | ## Steps to Reproduce the Problem 8 | 9 | 1. _ 10 | 2. _ 11 | 3. _ 12 | 13 | ## Specifications 14 | 15 | (The version of the project, operating system, hardware etc.) 16 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # PR 2 | 3 | Issue Fixed # 4 | 5 | ## Proposed Changes 6 | 7 | - _ 8 | - _ 9 | - _ 10 | 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### *.iml template 3 | node_modules/ 4 | package-lock.json 5 | ### .git template 6 | 7 | 8 | ### Tags template 9 | # Ignore tags created by etags, ctags, gtags (GNU global) and cscope 10 | TAGS 11 | !TAGS/ 12 | tags 13 | !tags/ 14 | gtags.files 15 | GTAGS 16 | GRTAGS 17 | GPATH 18 | cscope.files 19 | cscope.out 20 | cscope.in.out 21 | cscope.po.out 22 | 23 | 24 | 25 | ### Xcode template 26 | build/ 27 | *.pbxuser 28 | !default.pbxuser 29 | *.mode1v3 30 | !default.mode1v3 31 | *.mode2v3 32 | !default.mode2v3 33 | *.perspectivev3 34 | !default.perspectivev3 35 | xcuserdata 36 | *.xccheckout 37 | *.moved-aside 38 | DerivedData 39 | *.xcuserstate 40 | 41 | 42 | ### Java template 43 | *.class 44 | 45 | # Mobile Tools for Java (J2ME) 46 | .mtj.tmp/ 47 | 48 | # Package Files # 49 | *.jar 50 | *.war 51 | *.ear 52 | 53 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 54 | hs_err_pid* 55 | 56 | 57 | ### *.classpath template 58 | 59 | 60 | ### JetBrains template 61 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 62 | 63 | *.iml 64 | 65 | ## Directory-based project format: 66 | .idea/ 67 | # if you remove the above rule, at least ignore the following: 68 | 69 | # User-specific stuff: 70 | .idea/workspace.xml 71 | .idea/tasks.xml 72 | .idea/dictionaries 73 | 74 | # Sensitive or high-churn files: 75 | .idea/dataSources.ids 76 | .idea/dataSources.xml 77 | .idea/sqlDataSources.xml 78 | .idea/dynamic.xml 79 | .idea/uiDesigner.xml 80 | 81 | # Gradle: 82 | # .idea/gradle.xml 83 | # .idea/libraries 84 | 85 | # Mongo Explorer plugin: 86 | # .idea/mongoSettings.xml 87 | 88 | ## File-based project format: 89 | *.ipr 90 | *.iws 91 | 92 | ## Plugin-specific files: 93 | 94 | # IntelliJ 95 | out/ 96 | 97 | # mpeltonen/sbt-idea plugin 98 | .idea_modules/ 99 | 100 | # JIRA plugin 101 | atlassian-ide-plugin.xml 102 | 103 | # Crashlytics plugin (for Android Studio and IntelliJ) 104 | com_crashlytics_export_strings.xml 105 | crashlytics.properties 106 | crashlytics-build.properties 107 | 108 | 109 | ### Vim template 110 | [._]*.s[a-w][a-z] 111 | [._]s[a-w][a-z] 112 | *.un~ 113 | Session.vim 114 | .netrwhist 115 | *~ 116 | 117 | 118 | ### *.settings template 119 | *.settings 120 | *.settings/ 121 | 122 | ### Maven template 123 | target/ 124 | pom.xml.tag 125 | pom.xml.releaseBackup 126 | pom.xml.versionsBackup 127 | pom.xml.next 128 | release.properties 129 | dependency-reduced-pom.xml 130 | 131 | 132 | ### Linux template 133 | *~ 134 | 135 | # KDE directory preferences 136 | .directory 137 | 138 | # Linux trash folder which might appear on any partition or disk 139 | .Trash-* 140 | 141 | 142 | ### OSX template 143 | .DS_Store 144 | .AppleDouble 145 | .LSOverride 146 | 147 | # Icon must end with two \r 148 | Icon 149 | 150 | # Thumbnails 151 | ._* 152 | 153 | # Files that might appear in the root of a volume 154 | .DocumentRevisions-V100 155 | .fseventsd 156 | .Spotlight-V100 157 | .TemporaryItems 158 | .Trashes 159 | .VolumeIcon.icns 160 | 161 | # Directories potentially created on remote AFP share 162 | .AppleDB 163 | .AppleDesktop 164 | Network Trash Folder 165 | Temporary Items 166 | .apdisk 167 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2022 D0n9X1n 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included 11 | in all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 15 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 16 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 18 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 19 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | # hexo-blog-encrypt 2 | 3 | ![GitHub release (latest SemVer including pre-releases)](https://img.shields.io/github/v/release/D0n9x1n/hexo-blog-encrypt?include_prereleases) 4 | [![Build Status](https://scrutinizer-ci.com/g/MikeCoder/hexo-blog-encrypt/badges/build.png?b=master)](https://scrutinizer-ci.com/g/MikeCoder/hexo-blog-encrypt/build-status/master) 5 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/MikeCoder/hexo-blog-encrypt/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/MikeCoder/hexo-blog-encrypt/?branch=master) 6 | 7 | [中文说明](./ReadMe.zh.md) 8 | 9 | ## What's this 10 | 11 | - ~~First of all, the **BEST** post encryption plugin in the universe for hexo.(But what about the other plugins?)~~ 12 | 13 | - It is for those who write a post, but don't want everyone to read it. Thus, password is required in certain pages to access these encrypted posts. 14 | 15 | - Encryption is simple on wordpress, emlog or other blog systems, except hexo. :( 16 | 17 | - So it's "hexo-blog-encrypt"'s time. 18 | 19 | ## Features 20 | 21 | - Once you enter the correct password, you can get the access to encrypted posts, and the password is remembered locally. Press the button again, and the stored password will be erased. If there're scripts in the post, they will be executed once the post is decrypted. 22 | 23 | - Support preset tag-specified password. 24 | 25 | - All functions are provided by the native APIs. We use [Crypto](https://nodejs.org/dist/latest-v12.x/docs/api/crypto.html) in Node.js, and use [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) in Browsers. 26 | 27 | - [PBKDF2](https://tools.ietf.org/html/rfc2898), [SHA256](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf) is used to derive keys, We use [AES256-CBC](https://csrc.nist.gov/publications/detail/sp/800-38a/final) to encrypt and decrypt data, we also use [HMAC](https://csrc.nist.gov/csrc/media/publications/fips/198/1/final/documents/fips-198-1_final.pdf) to verify message authentication codes to make sure the posts are decrypted well and not modified. 28 | 29 | - Promise is widely used to make sure our main procedures are asynchronous, so that there is little chance for the process to be blocked, and the experience will be more fluent. 30 | 31 | - Template theme supported, you can use [`default`, `blink`, `flip`, `shrink`, `surge`, `up`, `wave`, `xray`] to set up your template theme, and [CHECK ONLINE](https://mhexo.github.io/tags/ThemeTests/). 32 | 33 | - Outdated browsers may not work well. In such case, please upgrade your browser. 34 | 35 | ## Online demo 36 | 37 | - See [Demo Page](https://mhexo.github.io/), **all passwords are `hello`**. 38 | 39 | ## Install 40 | 41 | - `npm install --save hexo-blog-encrypt` 42 | 43 | - or `yarn add hexo-blog-encrypt` (require [Yarn](https://yarnpkg.com/en/)) 44 | 45 | ## Quick start 46 | 47 | - Add the "password" value to your post's front matter like: 48 | 49 | ```markdown 50 | 51 | --- 52 | title: Hello World 53 | date: 2016-03-30 21:18:02 54 | password: hello 55 | --- 56 | 57 | ``` 58 | 59 | - Then use `hexo clean && hexo g && hexo s` to see your encrypted post locally. 60 | 61 | ## Password Priority 62 | 63 | post's front matter > encrypt tags 64 | 65 | ## Advanced settings 66 | 67 | ### in post's front matter 68 | 69 | ```markdown 70 | 71 | --- 72 | title: Hello World 73 | tags: 74 | - encryptAsDiary 75 | date: 2016-03-30 21:12:21 76 | password: mikemessi 77 | abstract: Here's something encrypted, password is required to continue reading. 78 | message: Hey, password is required here. 79 | wrong_pass_message: Oh, this is an invalid password. Check and try again, please. 80 | wrong_hash_message: Oh, these decrypted content cannot be verified, but you can still have a look. 81 | --- 82 | 83 | ``` 84 | 85 | ### In `_config.yml` 86 | 87 | #### Example 88 | 89 | ```yaml 90 | # Security 91 | encrypt: # hexo-blog-encrypt 92 | abstract: Here's something encrypted, password is required to continue reading. 93 | message: Hey, password is required here. 94 | tags: 95 | - {name: encryptAsDiary, password: passwordA} 96 | - {name: encryptAsTips, password: passwordB} 97 | wrong_pass_message: Oh, this is an invalid password. Check and try again, please. 98 | wrong_hash_message: Oh, these decrypted content cannot be verified, but you can still have a look. 99 | 100 | ``` 101 | 102 | #### To disable tag encryption 103 | 104 | Just set the `password` property in front matter to `""`. 105 | 106 | Example: 107 | 108 | ``` 109 | --- 110 | title: Callback Test 111 | date: 2019-12-21 11:54:07 112 | tags: 113 | - A Tag should be encrypted 114 | password: "" 115 | --- 116 | 117 | Use a "" to disable tag encryption. 118 | ``` 119 | 120 | ### Config priority 121 | 122 | post's front matter > `_config.yml` (in the root directory) > default 123 | 124 | ### About Callback 125 | In some blogs, some elements may not be displayed normally after decryption. This is a known issue. The current solution is to check the code in your blog to learn which functions are called when the onload event occurs. 126 | Then write these code at the end of your post. For example: 127 | 128 | ``` 129 | --- 130 | title: Callback Test 131 | date: 2019-12-21 11:54:07 132 | tags: 133 | - Encrypted 134 | --- 135 | 136 | This is a blog to test Callback functions. You just need to add code at the end of your post as follows: 137 | 138 | It will be called after the blog is decrypted. 139 | 140 | 144 | ``` 145 | 146 | Demo: [Callback Example](https://mhexo.github.io/2020/12/06/Callback-Test/). 147 | 148 | ### After Decrypt Event 149 | Thanks to @[f-dong](https://github.com/f-dong), we now will trigger a event named `hexo-blog-decrypt`, so you can add a call back to listen to that event. 150 | 151 | ``` 152 | // trigger event 153 | var event = new Event('hexo-blog-decrypt'); 154 | window.dispatchEvent(event); 155 | ``` 156 | 157 | ### Encrypt TOC 158 | 159 | If you has a post with TOC, you should change the code of your template. Take the default theme 'landscape' as an example: 160 | 161 | + You should find the `article.ejs` file located at `hexo/themes/landscape/layout/_partial/article.ejs`. 162 | + Find the code like <% post.content %>, which is usually at line 30. 163 | + Replace the <% post.content %> with the following code block: 164 | 165 | ``` 166 | <% if(post.toc == true){ %> 167 |
style="display:none" <% } %>> 168 | Index 169 | <% if (post.encrypt == true) { %> 170 | <%- toc(post.origin, {list_number: true}) %> 171 | <% } else { %> 172 | <%- toc(post.content, {list_number: true}) %> 173 | <% } %> 174 |
175 | <% } %> 176 | <%- post.content %> 177 | ``` 178 | 179 | ### Disable logging 180 | If you want to disable the logging, you can add a silent property in `_config.yml` and set it to true. 181 | 182 | ```yaml 183 | # Security 184 | encrypt: # hexo-blog-encrypt 185 | silent: true 186 | ``` 187 | 188 | This would disable the logging like `INFO hexo-blog-encrypt: encrypting "{Blog Name}" based on Tag: "EncryptedTag".`. 189 | 190 | ### Encrypt Theme 191 | Previously, we use `template` to let users modify their own themes. Turn out that it's not a simple way. So, we are introducing this feature here. 192 | 193 | You can simply use `theme` in `_config.yml` or in header like: 194 | 195 | #### In post's front matter 196 | 197 | ```markdown 198 | --- 199 | title: Theme test 200 | date: 2019-12-21 11:54:07 201 | tags: 202 | - A Tag should be encrypted 203 | theme: xray 204 | password: "hello" 205 | --- 206 | ``` 207 | 208 | #### In `_config.yml` 209 | 210 | This would be a default one. 211 | 212 | ```yaml 213 | # Security 214 | encrypt: # hexo-blog-encrypt 215 | abstract: Here's something encrypted, password is required to continue reading. 216 | message: Hey, password is required here. 217 | tags: 218 | - {name: encryptAsDiary, password: passwordA} 219 | - {name: encryptAsTips, password: passwordB} 220 | theme: xray 221 | wrong_pass_message: Oh, this is an invalid password. Check and try again, please. 222 | wrong_hash_message: Oh, these decrypted content cannot be verified, but you can still have a look. 223 | 224 | ``` 225 | 226 | Check them online, and PICK one: 227 | 228 | + [default](https://mhexo.github.io/2020/12/23/Theme-Test-Default/) 229 | + [blink](https://mhexo.github.io/2020/12/23/Theme-Test-Blink/) 230 | + [shrink](https://mhexo.github.io/2020/12/23/Theme-Test-Shrink/) 231 | + [flip](https://mhexo.github.io/2020/12/23/Theme-Test-Flip/) 232 | + [up](https://mhexo.github.io/2020/12/23/Theme-Test-Up/) 233 | + [surge](https://mhexo.github.io/2020/12/23/Theme-Test-Surge/) 234 | + [wave](https://mhexo.github.io/2020/12/23/Theme-Test-Wave/) 235 | + [xray](https://mhexo.github.io/2020/12/23/Theme-Test-Xray/) 236 | 237 | 238 | ## License 239 | 240 | See [LICENSE](./LICENSE) file. 241 | 242 | ## Thanks 243 | 244 | Collaborator - [xiazeyu](https://github.com/xiazeyu) 245 | -------------------------------------------------------------------------------- /ReadMe.zh.md: -------------------------------------------------------------------------------- 1 | # hexo-blog-encrypt 2 | 3 | ![GitHub release (latest SemVer including pre-releases)](https://img.shields.io/github/v/release/D0n9x1n/hexo-blog-encrypt?include_prereleases) 4 | [![Build Status](https://scrutinizer-ci.com/g/MikeCoder/hexo-blog-encrypt/badges/build.png?b=master)](https://scrutinizer-ci.com/g/MikeCoder/hexo-blog-encrypt/build-status/master) 5 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/MikeCoder/hexo-blog-encrypt/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/MikeCoder/hexo-blog-encrypt/?branch=master) 6 | 7 | #### 提 issue 之前,请务必提供复现方式,log,配置信息等必要信息。良好的 issue 可以节省双方的时间。 8 | *请严格按照模版要求,不明确的 issue 将直接关闭。* 9 | 10 | **参考 issue:** 11 | 12 | > + [Issue 83](https://github.com/MikeCoder/hexo-blog-encrypt/issues/83) 13 | > + [Issue 68](https://github.com/MikeCoder/hexo-blog-encrypt/issues/68) 14 | 15 | ## 这是个啥 16 | 17 | - ~~首先, 这是 Hexo 生态圈中 **最好的** 博客加密插件~~ 18 | 19 | - 你可能需要写一些私密的博客, 通过密码验证的方式让人不能随意浏览. 20 | 21 | - 这在 wordpress, emlog 或是其他博客系统中都很容易实现, 然而 hexo 除外. :( 22 | 23 | - 为了解决这个问题, 让我们有请 "hexo-blog-encrypt". 24 | 25 | ## 特性 26 | 27 | - 一旦你输入了正确的密码, 它将会被存储在本地浏览器的 localStorage中. 按个按钮, 密码将会被清空. 若博客中有脚本, 它将会被正确地执行. 28 | 29 | - 支持按标签加密. 30 | 31 | - 所有的核心功能都是由原生的 API 所提供的. 在 Node.js中, 我们使用 [Crypto](https://nodejs.org/dist/latest-v12.x/docs/api/crypto.html). 在浏览器中, 我们使用 [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API). 32 | 33 | - [PBKDF2](https://tools.ietf.org/html/rfc2898), [SHA256](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf) 被用于分发密钥, [AES256-CBC](https://csrc.nist.gov/publications/detail/sp/800-38a/final) 被用于加解密, 我们还使用 [HMAC](https://csrc.nist.gov/csrc/media/publications/fips/198/1/final/documents/fips-198-1_final.pdf) 来验证密文的来源, 并确保其未被篡改. 34 | 35 | - 我们广泛地使用 Promise 来进行异步操作, 以此确保线程不被阻塞. 36 | 37 | - 加密页面多主题支持, 现在已经支持的主题有 [`default`, `xray`], 更多的主题正在开发中. 38 | 39 | - 过时的浏览器将不能正常显示, 因此, 请升级您的浏览器. 40 | 41 | ## 在线演示 42 | 43 | - 点击 [Demo Page](https://mhexo.github.io/), **所有的密码都是 `hello`**. 44 | 45 | ## 安装 46 | 47 | - `npm install --save hexo-blog-encrypt` 48 | 49 | - 或 `yarn add hexo-blog-encrypt` (需要) [Yarn](https://yarnpkg.com/en/)) 50 | 51 | ## 快速使用 52 | 53 | - 将 "password" 字段添加到您文章信息头就像这样. 54 | 55 | ```markdown 56 | 57 | --- 58 | title: Hello World 59 | date: 2016-03-30 21:18:02 60 | password: hello 61 | --- 62 | 63 | ``` 64 | 65 | - 再使用 `hexo clean && hexo g && hexo s` 在本地预览加密的文章. 66 | 67 | ## 设置优先级 68 | 69 | 文章信息头 > 按标签加密 70 | 71 | ## 高级设置 72 | 73 | ### 文章信息头 74 | 75 | ```markdown 76 | 77 | --- 78 | title: Hello World 79 | tags: 80 | - 作为日记加密 81 | date: 2016-03-30 21:12:21 82 | password: mikemessi 83 | abstract: 有东西被加密了, 请输入密码查看. 84 | message: 您好, 这里需要密码. 85 | wrong_pass_message: 抱歉, 这个密码看着不太对, 请再试试. 86 | wrong_hash_message: 抱歉, 这个文章不能被校验, 不过您还是能看看解密后的内容. 87 | --- 88 | 89 | ``` 90 | 91 | ### `_config.yml` 92 | 93 | #### 示例 94 | 95 | ```yaml 96 | 97 | # Security 98 | encrypt: # hexo-blog-encrypt 99 | abstract: 有东西被加密了, 请输入密码查看. 100 | message: 您好, 这里需要密码. 101 | tags: 102 | - {name: tagName, password: 密码A} 103 | - {name: tagName, password: 密码B} 104 | wrong_pass_message: 抱歉, 这个密码看着不太对, 请再试试. 105 | wrong_hash_message: 抱歉, 这个文章不能被校验, 不过您还是能看看解密后的内容. 106 | 107 | ``` 108 | 109 | #### 对博文禁用 Tag 加密 110 | 111 | 只需要将博文头部的 `password` 设置为 `""` 即可取消 Tag 加密. 112 | 113 | Example: 114 | 115 | ``` 116 | --- 117 | title: Callback Test 118 | date: 2019-12-21 11:54:07 119 | tags: 120 | - A Tag should be encrypted 121 | password: "" 122 | --- 123 | 124 | Use a "" to diable tag encryption. 125 | ``` 126 | 127 | ### 配置优先级 128 | 129 | 文章信息头 > `_config.yml` (站点根目录下的) > 默认配置 130 | 131 | ### 关于 Callback 函数 132 | 在部分博客中, 解密后部分元素可能无法正常显示或者表现, 这属于已知问题. 目前的解决办法是通过自行查阅自己的博客中的代码, 了解到在 onload 事件发生时调用了哪些函数, 并将这些函数挑选后写入到博客内容中. 如: 133 | 134 | ``` 135 | --- 136 | title: Callback Test 137 | date: 2019-12-21 11:54:07 138 | tags: 139 | - Encrypted 140 | --- 141 | 142 | This is a blog to test Callback functions. You just need to add code at the last of your post like following: 143 | 144 | It will be called after the blog decrypted. 145 | 146 | 150 | ``` 151 | 152 | 例子在: [Callback 例子](https://mhexo.github.io/2020/12/06/Callback-Test/). 153 | 154 | ### 解密后的触发事件 155 | 感谢 @[f-dong](https://github.com/f-dong), 我们现在会在解密完成后触发一个 `hexo-blog-decrypt` 事件, 你们可以编写 callback 来监听该事件. 156 | 157 | ``` 158 | // trigger event 159 | var event = new Event('hexo-blog-decrypt'); 160 | window.dispatchEvent(event); 161 | ``` 162 | 163 | ### 对 TOC 进行加密 164 | 165 | 如果你有一篇文章使用了 TOC,你需要修改模板的部分代码。这里用 landscape 作为例子: 166 | 167 | + 你可以在 hexo/themes/landscape/layout/_partial/article.ejs 找到 article.ejs。 168 | + 然后找到 <% post.content %> 这段代码,通常在30行左右。 169 | + 使用如下的代码来替代它: 170 | 171 | ``` 172 | <% if(post.toc == true){ %> 173 |
style="display:none" <% } %>> 174 | Index 175 | <% if (post.encrypt == true) { %> 176 | <%- toc(post.origin, {list_number: true}) %> 177 | <% } else { %> 178 | <%- toc(post.content, {list_number: true}) %> 179 | <% } %> 180 |
181 | <% } %> 182 | <%- post.content %> 183 | ``` 184 | 185 | ### 禁用 Log 186 | If you want to disable the logging, you can add a silent property in `_config.yml` and set it to true. 187 | 如果你想要禁止使用 Log, 你可以在 `_config.yml` 中增加一个 silent 属性, 并将其设置为 true. 188 | 189 | ```yaml 190 | # Security 191 | encrypt: # hexo-blog-encrypt 192 | silent: true 193 | ``` 194 | 195 | 这样就会禁止如 `INFO hexo-blog-encrypt: encrypting "{Blog Name}" based on Tag: "EncryptedTag".` 的日志. 196 | 197 | ### 加密主题 198 | 199 | 之前, 我们尝试使用 `template` 关键字来让用户能修改自己的主题. 后来发现真不是一个好主意. 所以我们现在引入了主题: `theme` 关键字. 200 | 201 | 你可以简单的使用 `theme` 在 `_config.yml` 里或者文章头, 如下: 202 | 203 | ### 文章信息头 204 | 205 | ```markdown 206 | 207 | --- 208 | title: Hello World 209 | tags: 210 | - 作为日记加密 211 | date: 2016-03-30 21:12:21 212 | password: mikemessi 213 | abstract: 有东西被加密了, 请输入密码查看. 214 | message: 您好, 这里需要密码. 215 | theme: xray 216 | wrong_pass_message: 抱歉, 这个密码看着不太对, 请再试试. 217 | wrong_hash_message: 抱歉, 这个文章不能被校验, 不过您还是能看看解密后的内容. 218 | --- 219 | 220 | ``` 221 | 222 | ### 在 `_config.yml` 223 | 224 | #### 示例 225 | 226 | ```yaml 227 | 228 | # Security 229 | encrypt: # hexo-blog-encrypt 230 | abstract: 有东西被加密了, 请输入密码查看. 231 | message: 您好, 这里需要密码. 232 | tags: 233 | - {name: tagName, password: 密码A} 234 | - {name: tagName, password: 密码B} 235 | theme: xray 236 | wrong_pass_message: 抱歉, 这个密码看着不太对, 请再试试. 237 | wrong_hash_message: 抱歉, 这个文章不能被校验, 不过您还是能看看解密后的内容. 238 | 239 | ``` 240 | 241 | 你可以在线挑选你喜欢的主题,并应用到你的博客中: 242 | 243 | + [default](https://mhexo.github.io/2020/12/23/Theme-Test-Default/) 244 | + [blink](https://mhexo.github.io/2020/12/23/Theme-Test-Blink/) 245 | + [shrink](https://mhexo.github.io/2020/12/23/Theme-Test-Shrink/) 246 | + [flip](https://mhexo.github.io/2020/12/23/Theme-Test-Flip/) 247 | + [up](https://mhexo.github.io/2020/12/23/Theme-Test-Up/) 248 | + [surge](https://mhexo.github.io/2020/12/23/Theme-Test-Surge/) 249 | + [wave](https://mhexo.github.io/2020/12/23/Theme-Test-Wave/) 250 | + [xray](https://mhexo.github.io/2020/12/23/Theme-Test-Xray/) 251 | 252 | 253 | ## 许可 254 | 255 | 看看 [LICENSE](./LICENSE). 256 | 257 | ## 感谢 258 | 259 | Collaborator - [xiazeyu](https://github.com/xiazeyu) 260 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* global hexo, __dirname */ 2 | 3 | 'use strict'; 4 | 5 | const crypto = require('crypto'); 6 | const fs = require('fs'); 7 | const path = require('path'); 8 | const log = hexo.log; 9 | 10 | const defaultConfig = { 11 | 'abstract': 'Here\'s something encrypted, password is required to continue reading.', 12 | 'message': 'Hey, password is required here.', 13 | 'theme': 'default', 14 | 'wrong_pass_message': 'Oh, this is an invalid password. Check and try again, please.', 15 | 'wrong_hash_message': 'OOPS, these decrypted content may changed, but you can still have a look.', 16 | 'silent': false, 17 | }; 18 | 19 | const keySalt = crypto.randomBytes(32); 20 | const ivSalt = crypto.randomBytes(32); 21 | 22 | // As we can't detect the wrong password with AES-CBC, 23 | // so adding an empty tag and check it when decrption. 24 | const knownPrefix = ""; 25 | 26 | // disable log 27 | var silent = false; 28 | // use default theme 29 | var theme = 'default'; 30 | 31 | hexo.extend.filter.register('after_post_render', (data) => { 32 | const tagEncryptPairs = []; 33 | 34 | let password = data.password; 35 | let tagUsed = false; 36 | 37 | // use a empty password to disable category encryption 38 | if (password === "") { 39 | return data; 40 | } 41 | 42 | if (hexo.config.encrypt === undefined) { 43 | hexo.config.encrypt = []; 44 | } 45 | 46 | if(('encrypt' in hexo.config) && ('tags' in hexo.config.encrypt)){ 47 | hexo.config.encrypt.tags.forEach((tagObj) => { 48 | tagEncryptPairs[tagObj.name] = tagObj.password; 49 | }); 50 | } 51 | 52 | if (data.tags) { 53 | data.tags.forEach((cTag) => { 54 | if (tagEncryptPairs.hasOwnProperty(cTag.name)) { 55 | tagUsed = password ? tagUsed : cTag.name; 56 | password = password || tagEncryptPairs[cTag.name]; 57 | } 58 | }); 59 | } 60 | 61 | if(password == undefined){ 62 | return data; 63 | } 64 | 65 | password = password.toString(); 66 | 67 | // make sure toc can work. 68 | data.origin = data.content; 69 | 70 | // Let's rock n roll 71 | const config = Object.assign(defaultConfig, hexo.config.encrypt, data); 72 | silent = config.silent; 73 | theme = config.theme.trim().toLowerCase(); 74 | 75 | // deprecate the template keyword 76 | if (config.template) { 77 | dlog('warn', 'Looks like you use a deprecated property "template" to set up template, consider to use "theme"? See https://github.com/D0n9X1n/hexo-blog-encrypt#encrypt-theme'); 78 | } 79 | 80 | // read theme from file 81 | let template = fs.readFileSync(path.resolve(__dirname, `./lib/hbe.${theme}.html`)).toString(); 82 | 83 | if (tagUsed === false) { 84 | dlog('info', `hexo-blog-encrypt: encrypting "${data.title.trim()}" based on the password configured in Front-matter with theme: ${theme}.`); 85 | } else { 86 | dlog('info', `hexo-blog-encrypt: encrypting "${data.title.trim()}" based on Tag: "${tagUsed}" with theme ${theme}.`); 87 | } 88 | 89 | data.content = knownPrefix + data.content.trim(); 90 | data.encrypt = true; 91 | 92 | const key = crypto.pbkdf2Sync(password, keySalt, 1024, 32, 'sha256'); 93 | const iv = crypto.pbkdf2Sync(password, ivSalt, 512, 16, 'sha256'); 94 | 95 | const cipher = crypto.createCipheriv('aes-256-cbc', key, iv); 96 | const hmac = crypto.createHmac('sha256', key); 97 | 98 | let encryptedData = cipher.update(data.content, 'utf8', 'hex'); 99 | hmac.update(data.content, 'utf8'); 100 | encryptedData += cipher.final('hex'); 101 | const hmacDigest = hmac.digest('hex'); 102 | 103 | data.content = template.replace(/{{hbeEncryptedData}}/g, encryptedData) 104 | .replace(/{{hbeHmacDigest}}/g, hmacDigest) 105 | .replace(/{{hbeWrongPassMessage}}/g, config.wrong_pass_message) 106 | .replace(/{{hbeWrongHashMessage}}/g, config.wrong_hash_message) 107 | .replace(/{{hbeMessage}}/g, config.message) 108 | .replace(/{{hbeKeySalt}}/g, keySalt.toString('hex')) 109 | .replace(/{{hbeIvSalt}}/g, ivSalt.toString('hex')); 110 | data.content += ``; 111 | data.excerpt = data.more = config.abstract; 112 | 113 | return data; 114 | }, 1000); 115 | 116 | hexo.extend.generator.register('hexo-blog-encrypt', () => [ 117 | { 118 | 'data': () => fs.createReadStream(path.resolve(__dirname, `./lib/hbe.style.css`)), 119 | 'path': `css/hbe.style.css`, 120 | }, 121 | { 122 | 'data': () => fs.createReadStream(path.resolve(__dirname, './lib/hbe.js')), 123 | 'path': 'lib/hbe.js', 124 | }, 125 | ]); 126 | 127 | // log function 128 | function dlog(level, x) { 129 | switch (level) { 130 | case 'warn': 131 | log.warn(x); 132 | break; 133 | 134 | case 'info': 135 | default: 136 | if (silent) { 137 | return; 138 | } 139 | 140 | log.info(x); 141 | } 142 | } 143 | 144 | // Utils functions 145 | function textToArray(s) { 146 | var i = s.length; 147 | var n = 0; 148 | var ba = new Array() 149 | 150 | for (var j = 0; j < i;) { 151 | var c = s.codePointAt(j); 152 | if (c < 128) { 153 | ba[n++] = c; 154 | j++; 155 | } else if ((c > 127) && (c < 2048)) { 156 | ba[n++] = (c >> 6) | 192; 157 | ba[n++] = (c & 63) | 128; 158 | j++; 159 | } else if ((c > 2047) && (c < 65536)) { 160 | ba[n++] = (c >> 12) | 224; 161 | ba[n++] = ((c >> 6) & 63) | 128; 162 | ba[n++] = (c & 63) | 128; 163 | j++; 164 | } else { 165 | ba[n++] = (c >> 18) | 240; 166 | ba[n++] = ((c >> 12) & 63) | 128; 167 | ba[n++] = ((c >> 6) & 63) | 128; 168 | ba[n++] = (c & 63) | 128; 169 | j += 2; 170 | } 171 | } 172 | 173 | return new Uint8Array(ba); 174 | } 175 | -------------------------------------------------------------------------------- /lib/hbe.blink.html: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 | 12 |
13 |
14 | -------------------------------------------------------------------------------- /lib/hbe.default.html: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 |
7 | 8 | 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /lib/hbe.flip.html: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 |
7 | 8 | 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /lib/hbe.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | 'use strict'; 3 | 4 | const cryptoObj = window.crypto || window.msCrypto; 5 | const storage = window.localStorage; 6 | 7 | const storageName = 'hexo-blog-encrypt:#' + window.location.pathname; 8 | 9 | // As we can't detect the wrong password with AES-CBC, 10 | // so adding an empty div and check it when decrption. 11 | const knownPrefix = ""; 12 | 13 | const mainElement = document.getElementById('hexo-blog-encrypt'); 14 | const wrongPassMessage = mainElement.dataset['wpm']; 15 | const wrongHashMessage = mainElement.dataset['whm']; 16 | const dataElement = mainElement.getElementsByTagName('script')['hbeData']; 17 | const encryptedData = dataElement.innerText; 18 | const HmacDigist = dataElement.dataset['hmacdigest']; 19 | // If the plugin version is updated but the blog is not regenerated (e.g. caching), the legacy fixed salt value is used. 20 | const keySalt = dataElement.dataset['keysalt'] ? hexToArray(dataElement.dataset['keysalt']) : textToArray('hexo-blog-encrypt的作者们都是大帅比!'); 21 | const ivSalt = dataElement.dataset['ivsalt'] ? hexToArray(dataElement.dataset['ivsalt']) : textToArray('hexo-blog-encrypt是地表最强Hexo加密插件!'); 22 | 23 | function hexToArray(s) { 24 | return new Uint8Array(s.match(/[\da-f]{2}/gi).map((h => { 25 | return parseInt(h, 16); 26 | }))); 27 | } 28 | 29 | function textToArray(s) { 30 | var i = s.length; 31 | var n = 0; 32 | var ba = new Array() 33 | 34 | for (var j = 0; j < i;) { 35 | var c = s.codePointAt(j); 36 | if (c < 128) { 37 | ba[n++] = c; 38 | j++; 39 | } else if ((c > 127) && (c < 2048)) { 40 | ba[n++] = (c >> 6) | 192; 41 | ba[n++] = (c & 63) | 128; 42 | j++; 43 | } else if ((c > 2047) && (c < 65536)) { 44 | ba[n++] = (c >> 12) | 224; 45 | ba[n++] = ((c >> 6) & 63) | 128; 46 | ba[n++] = (c & 63) | 128; 47 | j++; 48 | } else { 49 | ba[n++] = (c >> 18) | 240; 50 | ba[n++] = ((c >> 12) & 63) | 128; 51 | ba[n++] = ((c >> 6) & 63) | 128; 52 | ba[n++] = (c & 63) | 128; 53 | j += 2; 54 | } 55 | } 56 | return new Uint8Array(ba); 57 | } 58 | 59 | function arrayBufferToHex(arrayBuffer) { 60 | if (typeof arrayBuffer !== 'object' || arrayBuffer === null || typeof arrayBuffer.byteLength !== 'number') { 61 | throw new TypeError('Expected input to be an ArrayBuffer') 62 | } 63 | 64 | var view = new Uint8Array(arrayBuffer) 65 | var result = '' 66 | var value 67 | 68 | for (var i = 0; i < view.length; i++) { 69 | value = view[i].toString(16) 70 | result += (value.length === 1 ? '0' + value : value) 71 | } 72 | 73 | return result 74 | } 75 | 76 | async function getExecutableScript(oldElem) { 77 | let out = document.createElement('script'); 78 | const attList = ['type', 'text', 'src', 'crossorigin', 'defer', 'referrerpolicy']; 79 | attList.forEach((att) => { 80 | if (oldElem[att]) 81 | out[att] = oldElem[att]; 82 | }) 83 | 84 | return out; 85 | } 86 | 87 | async function convertHTMLToElement(content) { 88 | let out = document.createElement('div'); 89 | out.innerHTML = content; 90 | out.querySelectorAll('script').forEach(async (elem) => { 91 | elem.replaceWith(await getExecutableScript(elem)); 92 | }); 93 | 94 | return out; 95 | } 96 | 97 | function getKeyMaterial(password) { 98 | let encoder = new TextEncoder(); 99 | return cryptoObj.subtle.importKey( 100 | 'raw', 101 | encoder.encode(password), 102 | { 103 | 'name': 'PBKDF2', 104 | }, 105 | false, 106 | [ 107 | 'deriveKey', 108 | 'deriveBits', 109 | ] 110 | ); 111 | } 112 | 113 | function getHmacKey(keyMaterial) { 114 | return cryptoObj.subtle.deriveKey({ 115 | 'name': 'PBKDF2', 116 | 'hash': 'SHA-256', 117 | 'salt': keySalt.buffer, 118 | 'iterations': 1024 119 | }, keyMaterial, { 120 | 'name': 'HMAC', 121 | 'hash': 'SHA-256', 122 | 'length': 256, 123 | }, true, [ 124 | 'verify', 125 | ]); 126 | } 127 | 128 | function getDecryptKey(keyMaterial) { 129 | return cryptoObj.subtle.deriveKey({ 130 | 'name': 'PBKDF2', 131 | 'hash': 'SHA-256', 132 | 'salt': keySalt.buffer, 133 | 'iterations': 1024, 134 | }, keyMaterial, { 135 | 'name': 'AES-CBC', 136 | 'length': 256, 137 | }, true, [ 138 | 'decrypt', 139 | ]); 140 | } 141 | 142 | function getIv(keyMaterial) { 143 | return cryptoObj.subtle.deriveBits({ 144 | 'name': 'PBKDF2', 145 | 'hash': 'SHA-256', 146 | 'salt': ivSalt.buffer, 147 | 'iterations': 512, 148 | }, keyMaterial, 16 * 8); 149 | } 150 | 151 | async function verifyContent(key, content) { 152 | const encoder = new TextEncoder(); 153 | const encoded = encoder.encode(content); 154 | 155 | let signature = hexToArray(HmacDigist); 156 | 157 | const result = await cryptoObj.subtle.verify({ 158 | 'name': 'HMAC', 159 | 'hash': 'SHA-256', 160 | }, key, signature, encoded); 161 | console.log(`Verification result: ${result}`); 162 | if (!result) { 163 | alert(wrongHashMessage); 164 | console.log(`${wrongHashMessage}, got `, signature, ` but proved wrong.`); 165 | } 166 | return result; 167 | } 168 | 169 | async function decrypt(decryptKey, iv, hmacKey) { 170 | let typedArray = hexToArray(encryptedData); 171 | 172 | const result = await cryptoObj.subtle.decrypt({ 173 | 'name': 'AES-CBC', 174 | 'iv': iv, 175 | }, decryptKey, typedArray.buffer).then(async (result) => { 176 | const decoder = new TextDecoder(); 177 | const decoded = decoder.decode(result); 178 | 179 | // check the prefix, if not then we can sure here is wrong password. 180 | if (!decoded.startsWith(knownPrefix)) { 181 | throw "Decode successfully but not start with KnownPrefix."; 182 | } 183 | 184 | const hideButton = document.createElement('button'); 185 | hideButton.textContent = 'Encrypt again'; 186 | hideButton.type = 'button'; 187 | hideButton.classList.add("hbe-button"); 188 | hideButton.addEventListener('click', () => { 189 | window.localStorage.removeItem(storageName); 190 | window.location.reload(); 191 | }); 192 | 193 | document.getElementById('hexo-blog-encrypt').style.display = 'inline'; 194 | document.getElementById('hexo-blog-encrypt').innerHTML = ''; 195 | document.getElementById('hexo-blog-encrypt').appendChild(await convertHTMLToElement(decoded)); 196 | document.getElementById('hexo-blog-encrypt').appendChild(hideButton); 197 | 198 | // support html5 lazyload functionality. 199 | document.querySelectorAll('img').forEach((elem) => { 200 | if (elem.getAttribute("data-src") && !elem.src) { 201 | elem.src = elem.getAttribute('data-src'); 202 | } 203 | }); 204 | 205 | // support theme-next refresh 206 | window.NexT && NexT.boot && typeof NexT.boot.refresh === 'function' && NexT.boot.refresh(); 207 | 208 | // TOC part 209 | var tocDiv = document.getElementById("toc-div"); 210 | if (tocDiv) { 211 | tocDiv.style.display = 'inline'; 212 | } 213 | 214 | var tocDivs = document.getElementsByClassName('toc-div-class'); 215 | if (tocDivs && tocDivs.length > 0) { 216 | for (var idx = 0; idx < tocDivs.length; idx++) { 217 | tocDivs[idx].style.display = 'inline'; 218 | } 219 | } 220 | 221 | // trigger event 222 | var event = new Event('hexo-blog-decrypt'); 223 | window.dispatchEvent(event); 224 | 225 | return await verifyContent(hmacKey, decoded); 226 | }).catch((e) => { 227 | alert(wrongPassMessage); 228 | console.log(e); 229 | return false; 230 | }); 231 | 232 | return result; 233 | 234 | } 235 | 236 | function hbeLoader() { 237 | 238 | const oldStorageData = JSON.parse(storage.getItem(storageName)); 239 | 240 | if (oldStorageData) { 241 | console.log(`Password got from localStorage(${storageName}): `, oldStorageData); 242 | 243 | const sIv = hexToArray(oldStorageData.iv).buffer; 244 | const sDk = oldStorageData.dk; 245 | const sHmk = oldStorageData.hmk; 246 | 247 | cryptoObj.subtle.importKey('jwk', sDk, { 248 | 'name': 'AES-CBC', 249 | 'length': 256, 250 | }, true, [ 251 | 'decrypt', 252 | ]).then((dkCK) => { 253 | cryptoObj.subtle.importKey('jwk', sHmk, { 254 | 'name': 'HMAC', 255 | 'hash': 'SHA-256', 256 | 'length': 256, 257 | }, true, [ 258 | 'verify', 259 | ]).then((hmkCK) => { 260 | decrypt(dkCK, sIv, hmkCK).then((result) => { 261 | if (!result) { 262 | storage.removeItem(storageName); 263 | } 264 | }); 265 | }); 266 | }); 267 | } 268 | 269 | mainElement.addEventListener('keydown', async (event) => { 270 | if (event.isComposing || event.keyCode === 13) { 271 | const password = document.getElementById('hbePass').value; 272 | const keyMaterial = await getKeyMaterial(password); 273 | const hmacKey = await getHmacKey(keyMaterial); 274 | const decryptKey = await getDecryptKey(keyMaterial); 275 | const iv = await getIv(keyMaterial); 276 | 277 | decrypt(decryptKey, iv, hmacKey).then((result) => { 278 | console.log(`Decrypt result: ${result}`); 279 | if (result) { 280 | cryptoObj.subtle.exportKey('jwk', decryptKey).then((dk) => { 281 | cryptoObj.subtle.exportKey('jwk', hmacKey).then((hmk) => { 282 | const newStorageData = { 283 | 'dk': dk, 284 | 'iv': arrayBufferToHex(iv), 285 | 'hmk': hmk, 286 | }; 287 | storage.setItem(storageName, JSON.stringify(newStorageData)); 288 | }); 289 | }); 290 | } 291 | }); 292 | } 293 | }); 294 | } 295 | 296 | hbeLoader(); 297 | 298 | })(); 299 | -------------------------------------------------------------------------------- /lib/hbe.shrink.html: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 |
7 | 8 | 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /lib/hbe.style.css: -------------------------------------------------------------------------------- 1 | .hbe, 2 | .hbe:after, 3 | .hbe:before { 4 | -webkit-box-sizing: border-box; 5 | -moz-box-sizing: border-box; 6 | box-sizing: border-box; 7 | } 8 | 9 | .hbe-container{ 10 | margin: 0 auto; 11 | overflow: hidden; 12 | } 13 | .hbe-content { 14 | text-align: center; 15 | font-size: 150%; 16 | padding: 1em 0; 17 | } 18 | 19 | .hbe-input { 20 | position: relative; 21 | z-index: 1; 22 | display: inline-block; 23 | margin: 1em; 24 | width: 80%; 25 | min-width: 200px; 26 | vertical-align: top; 27 | } 28 | 29 | .hbe-input-field { 30 | line-height: normal; 31 | font-size: 100%; 32 | margin: 0; 33 | position: relative; 34 | display: block; 35 | float: right; 36 | padding: 0.8em; 37 | width: 60%; 38 | border: none; 39 | border-radius: 0; 40 | background: #f0f0f0; 41 | color: #aaa; 42 | font-weight: 400; 43 | font-family: "Avenir Next", "Helvetica Neue", Helvetica, Arial, sans-serif; 44 | -webkit-appearance: none; /* for box shadows to show on iOS */ 45 | } 46 | 47 | .hbe-input-field:focus { 48 | outline: none; 49 | } 50 | 51 | .hbe-input-label { 52 | display: inline-block; 53 | float: right; 54 | padding: 0 1em; 55 | width: 40%; 56 | color: #696969; 57 | font-weight: bold; 58 | font-size: 70.25%; 59 | -webkit-font-smoothing: antialiased; 60 | -moz-osx-font-smoothing: grayscale; 61 | -webkit-touch-callout: none; 62 | -webkit-user-select: none; 63 | -khtml-user-select: none; 64 | -moz-user-select: none; 65 | -ms-user-select: none; 66 | user-select: none; 67 | } 68 | 69 | .hbe-input-label-content { 70 | position: relative; 71 | display: block; 72 | padding: 1.6em 0; 73 | width: 100%; 74 | } 75 | 76 | .hbe-graphic { 77 | position: absolute; 78 | top: 0; 79 | left: 0; 80 | fill: none; 81 | } 82 | 83 | /* hbe button in post page */ 84 | .hbe-button { 85 | width: 130px; 86 | height: 40px; 87 | background: linear-gradient(to bottom, #4eb5e5 0%,#389ed5 100%); /* W3C */ 88 | border: none; 89 | border-radius: 5px; 90 | position: relative; 91 | border-bottom: 4px solid #2b8bc6; 92 | color: #fbfbfb; 93 | font-weight: 600; 94 | font-family: 'Open Sans', sans-serif; 95 | text-shadow: 1px 1px 1px rgba(0,0,0,.4); 96 | font-size: 15px; 97 | text-align: left; 98 | text-indent: 5px; 99 | box-shadow: 0px 3px 0px 0px rgba(0,0,0,.2); 100 | cursor: pointer; 101 | 102 | display: block; 103 | margin: 0 auto; 104 | margin-bottom: 20px; 105 | } 106 | 107 | .hbe-button:active { 108 | box-shadow: 0px 2px 0px 0px rgba(0,0,0,.2); 109 | top: 1px; 110 | } 111 | 112 | .hbe-button:after { 113 | content: ""; 114 | width: 0; 115 | height: 0; 116 | display: block; 117 | border-top: 20px solid #187dbc; 118 | border-bottom: 20px solid #187dbc; 119 | border-left: 16px solid transparent; 120 | border-right: 20px solid #187dbc; 121 | position: absolute; 122 | opacity: 0.6; 123 | right: 0; 124 | top: 0; 125 | border-radius: 0 5px 5px 0; 126 | } 127 | /* hbe button in post page */ 128 | 129 | /* default theme {{{ */ 130 | .hbe-input-default { 131 | overflow: hidden; 132 | } 133 | 134 | .hbe-input-field-default { 135 | width: 100%; 136 | background: transparent; 137 | padding: 0.5em; 138 | margin-bottom: 2em; 139 | color: #f9f7f6; 140 | z-index: 100; 141 | opacity: 0; 142 | } 143 | 144 | .hbe-input-label-default { 145 | width: 100%; 146 | position: absolute; 147 | text-align: left; 148 | padding: 0.5em 0; 149 | pointer-events: none; 150 | font-size: 1em; 151 | } 152 | 153 | .hbe-input-label-default::before, 154 | .hbe-input-label-default::after { 155 | content: ''; 156 | position: absolute; 157 | width: 100%; 158 | left: 0; 159 | } 160 | 161 | .hbe-input-label-default::before { 162 | height: 100%; 163 | background: #666666; 164 | top: 0; 165 | -webkit-transform: translate3d(0, -100%, 0); 166 | transform: translate3d(0, -100%, 0); 167 | -webkit-transition: -webkit-transform 0.2s; 168 | transition: transform 0.2s; 169 | } 170 | 171 | .hbe-input-label-default::after { 172 | height: 2px; 173 | background: #666666; 174 | top: 100%; 175 | -webkit-transition: opacity 0.2s; 176 | transition: opacity 0.2s; 177 | } 178 | 179 | .hbe-input-label-content-default { 180 | padding: 0; 181 | -webkit-transform-origin: 0 0; 182 | transform-origin: 0 0; 183 | -webkit-transition: -webkit-transform 0.2s, color 0.2s; 184 | transition: transform 0.2s, color 0.2s; 185 | } 186 | 187 | .hbe-input-field-default:focus, 188 | .hbe-input--filled .hbe-input-field-default { 189 | opacity: 1; 190 | -webkit-transition: opacity 0s 0.2s; 191 | transition: opacity 0s 0.2s; 192 | } 193 | 194 | .hbe-input-label-default::before, 195 | .hbe-input-label-default::after, 196 | .hbe-input-label-content-default, 197 | .hbe-input-field-default:focus, 198 | .hbe-input--filled .hbe-input-field-default { 199 | -webkit-transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1); 200 | transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1); 201 | } 202 | 203 | .hbe-input-field-default:focus + .hbe-input-label-default::before, 204 | .hbe-input--filled .hbe-input-label-default::before { 205 | -webkit-transform: translate3d(0, 0, 0); 206 | transform: translate3d(0, 0, 0); 207 | } 208 | 209 | .hbe-input-field-default:focus + .hbe-input-label-default::after, 210 | .hbe-input--filled .hbe-input-label-default::after { 211 | opacity: 0; 212 | } 213 | 214 | .hbe-input-field-default:focus + .hbe-input-label-default .hbe-input-label-content-default, 215 | .hbe-input--filled .hbe-input-label-default .hbe-input-label-content-default { 216 | color: #555555; 217 | -webkit-transform: translate3d(0, 2.1em, 0) scale3d(0.65, 0.65, 1); 218 | transform: translate3d(0, 2.1em, 0) scale3d(0.65, 0.65, 1); 219 | } 220 | /* default theme }}} */ 221 | 222 | /* up theme {{{ */ 223 | .hbe-input-up { 224 | overflow: hidden; 225 | padding-top: 2em; 226 | } 227 | 228 | .hbe-input-field-up { 229 | width: 100%; 230 | background: transparent; 231 | opacity: 0; 232 | padding: 0.35em; 233 | z-index: 100; 234 | color: #837482; 235 | } 236 | 237 | .hbe-input-label-up { 238 | width: 100%; 239 | bottom: 0; 240 | position: absolute; 241 | pointer-events: none; 242 | text-align: left; 243 | color: #8E9191; 244 | padding: 0 0.5em; 245 | } 246 | 247 | .hbe-input-label-up::before { 248 | content: ''; 249 | position: absolute; 250 | width: 100%; 251 | height: 4em; 252 | top: 100%; 253 | left: 0; 254 | background: #fff; 255 | border-top: 4px solid #9B9F9F; 256 | -webkit-transform: translate3d(0, -3px, 0); 257 | transform: translate3d(0, -3px, 0); 258 | -webkit-transition: -webkit-transform 0.4s; 259 | transition: transform 0.4s; 260 | -webkit-transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1); 261 | transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1); 262 | } 263 | 264 | .hbe-input-label-content-up { 265 | padding: 0.5em 0; 266 | -webkit-transform-origin: 0% 100%; 267 | transform-origin: 0% 100%; 268 | -webkit-transition: -webkit-transform 0.4s, color 0.4s; 269 | transition: transform 0.4s, color 0.4s; 270 | -webkit-transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1); 271 | transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1); 272 | } 273 | 274 | .hbe-input-field-up:focus, 275 | .input--filled .hbe-input-field-up { 276 | cursor: text; 277 | opacity: 1; 278 | -webkit-transition: opacity 0s 0.4s; 279 | transition: opacity 0s 0.4s; 280 | } 281 | 282 | .hbe-input-field-up:focus + .hbe-input-label-up::before, 283 | .input--filled .hbe-input-label-up::before { 284 | -webkit-transition-delay: 0.05s; 285 | transition-delay: 0.05s; 286 | -webkit-transform: translate3d(0, -3.3em, 0); 287 | transform: translate3d(0, -3.3em, 0); 288 | } 289 | 290 | .hbe-input-field-up:focus + .hbe-input-label-up .hbe-input-label-content-up, 291 | .input--filled .hbe-input-label-content-up { 292 | color: #6B6E6E; 293 | -webkit-transform: translate3d(0, -3.3em, 0) scale3d(0.81, 0.81, 1); 294 | transform: translate3d(0, -3.3em, 0) scale3d(0.81, 0.81, 1); 295 | } 296 | /* up theme }}} */ 297 | 298 | /* wave theme {{{ */ 299 | .hbe-input-wave { 300 | overflow: hidden; 301 | padding-top: 1em; 302 | } 303 | 304 | .hbe-input-field-wave { 305 | padding: 0.5em 0em 0.25em; 306 | width: 100%; 307 | background: transparent; 308 | color: #9da8b2; 309 | font-size: 1.25em; 310 | } 311 | 312 | .hbe-input-label-wave { 313 | position: absolute; 314 | top: 0.95em; 315 | font-size: 0.85em; 316 | left: 0; 317 | display: block; 318 | width: 100%; 319 | text-align: left; 320 | padding: 0em; 321 | pointer-events: none; 322 | -webkit-transform-origin: 0 0; 323 | transform-origin: 0 0; 324 | -webkit-transition: -webkit-transform 0.2s 0.15s, color 1s; 325 | transition: transform 0.2s 0.15s, color 1s; 326 | -webkit-transition-timing-function: ease-out; 327 | transition-timing-function: ease-out; 328 | } 329 | 330 | .hbe-graphic-wave { 331 | stroke: #92989e; 332 | pointer-events: none; 333 | -webkit-transition: -webkit-transform 0.7s, stroke 0.7s; 334 | transition: transform 0.7s, stroke 0.7s; 335 | -webkit-transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1); 336 | transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1); 337 | } 338 | 339 | .hbe-input-field-wave:focus + .hbe-input-label-wave, 340 | .input--filled .hbe-input-label-wave { 341 | color: #333; 342 | -webkit-transform: translate3d(0, -1.25em, 0) scale3d(0.75, 0.75, 1); 343 | transform: translate3d(0, -1.25em, 0) scale3d(0.75, 0.75, 1); 344 | } 345 | 346 | .hbe-input-field-wave:focus ~ .hbe-graphic-wave, 347 | .input--filled .graphic-wave { 348 | stroke: #333; 349 | -webkit-transform: translate3d(-66.6%, 0, 0); 350 | transform: translate3d(-66.6%, 0, 0); 351 | } 352 | /* wave theme }}} */ 353 | 354 | /* flip theme {{{ */ 355 | .hbe-input-field-flip { 356 | width: 100%; 357 | background-color: #d0d1d0; 358 | border: 2px solid transparent; 359 | -webkit-transition: background-color 0.25s, border-color 0.25s; 360 | transition: background-color 0.25s, border-color 0.25s; 361 | } 362 | 363 | .hbe-input-label-flip { 364 | width: 100%; 365 | text-align: left; 366 | position: absolute; 367 | bottom: 100%; 368 | pointer-events: none; 369 | overflow: hidden; 370 | padding: 0 1.25em; 371 | -webkit-transform: translate3d(0, 3em, 0); 372 | transform: translate3d(0, 3em, 0); 373 | -webkit-transition: -webkit-transform 0.25s; 374 | transition: transform 0.25s ; 375 | -webkit-transition-timing-function: ease-in-out; 376 | transition-timing-function: ease-in-out; 377 | } 378 | 379 | .hbe-input-label-content-flip { 380 | color: #8B8C8B; 381 | padding: 0.25em 0; 382 | -webkit-transition: -webkit-transform 0.25s; 383 | transition: transform 0.25s; 384 | -webkit-transition-timing-function: ease-in-out; 385 | transition-timing-function: ease-in-out; 386 | } 387 | 388 | .hbe-input-label-content-flip::after { 389 | content: attr(data-content); 390 | position: absolute; 391 | font-weight: 800; 392 | bottom: 100%; 393 | left: 0; 394 | height: 100%; 395 | width: 100%; 396 | color: #666666; 397 | padding: 0.25em 0; 398 | letter-spacing: 1px; 399 | font-size: 1em; 400 | } 401 | 402 | .hbe-input-field-flip:focus + .hbe-input-label-flip, 403 | .input--filled .hbe-input-label-flip { 404 | -webkit-transform: translate3d(0, 0, 0); 405 | transform: translate3d(0, 0, 0); 406 | } 407 | 408 | .hbe-input-field-flip:focus + .hbe-input-label-flip .hbe-input-label-content-flip, 409 | .input--filled .hbe-input-label-content-flip { 410 | -webkit-transform: translate3d(0, 100%, 0); 411 | transform: translate3d(0, 100%, 0); 412 | } 413 | 414 | .hbe-input-field-flip:focus + .hbe-input-field-flip, 415 | .input--filled .hbe-input-field-flip { 416 | background-color: transparent; 417 | border-color: #666666; 418 | } 419 | /* flip theme }}} */ 420 | 421 | /* xray theme {{{ */ 422 | .hbe-input-xray { 423 | overflow: hidden; 424 | padding-bottom: 2.5em; 425 | } 426 | 427 | .hbe-input-field-xray { 428 | padding: 0; 429 | margin-top: 1.2em; 430 | width: 100%; 431 | background: transparent; 432 | color: #84AF9B ; 433 | font-size: 1.55em; 434 | } 435 | 436 | .hbe-input-label-xray { 437 | position: absolute; 438 | top: 2em; 439 | left: 0; 440 | display: block; 441 | width: 100%; 442 | text-align: left; 443 | padding: 0em; 444 | letter-spacing: 1px; 445 | color: #84AF9B ; 446 | pointer-events: none; 447 | -webkit-transform-origin: 0 0; 448 | transform-origin: 0 0; 449 | -webkit-transition: -webkit-transform 0.2s 0.1s, color 0.3s; 450 | transition: transform 0.2s 0.1s, color 0.3s; 451 | -webkit-transition-timing-function: ease-out; 452 | transition-timing-function: ease-out; 453 | } 454 | 455 | .hbe-graphic-xray { 456 | stroke: #84AF9B ; 457 | pointer-events: none; 458 | stroke-width: 2px; 459 | top: 1.25em; 460 | bottom: 0px; 461 | height: 3.275em; 462 | -webkit-transition: -webkit-transform 0.7s, stroke 0.7s; 463 | transition: transform 0.7s, stroke 0.7s; 464 | -webkit-transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1); 465 | transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1); 466 | } 467 | 468 | .hbe-input-field-xray:focus + .hbe-input-label-xray, 469 | .input--filled .hbe-input-label-xray { 470 | color: #84AF9B ; 471 | -webkit-transform: translate3d(0, 3.5em, 0) scale3d(0.85, 0.85, 1); 472 | transform: translate3d(0, 3.5em, 0) scale3d(0.85, 0.85, 1); 473 | } 474 | 475 | .hbe-input-field-xray:focus ~ .hbe-graphic-xray, 476 | .input--filled .graphic-xray { 477 | stroke: #84AF9B ; 478 | -webkit-transform: translate3d(-66.6%, 0, 0); 479 | transform: translate3d(-66.6%, 0, 0); 480 | } 481 | /* xray theme }}} */ 482 | 483 | /* blink theme {{{ */ 484 | .hbe-input-blink { 485 | padding-top: 1em; 486 | } 487 | 488 | .hbe-input-field-blink { 489 | width: 100%; 490 | padding: 0.8em 0.5em; 491 | background: transparent; 492 | border: 2px solid; 493 | color: #8781bd; 494 | -webkit-transition: border-color 0.25s; 495 | transition: border-color 0.25s; 496 | } 497 | 498 | .hbe-input-label-blink { 499 | width: 100%; 500 | position: absolute; 501 | top: 0; 502 | text-align: left; 503 | overflow: hidden; 504 | padding: 0; 505 | pointer-events: none; 506 | -webkit-transform: translate3d(0, 3em, 0); 507 | transform: translate3d(0, 3em, 0); 508 | } 509 | 510 | .hbe-input-label-content-blink { 511 | padding: 0 1em; 512 | font-weight: 400; 513 | color: #b5b5b5; 514 | } 515 | 516 | .hbe-input-label-content-blink::after { 517 | content: attr(data-content); 518 | position: absolute; 519 | top: -200%; 520 | left: 0; 521 | color: #8781bd ; 522 | font-weight: 800; 523 | } 524 | 525 | .hbe-input-field-blink:focus, 526 | .input--filled .hbe-input-field-blink { 527 | border-color: #8781bd ; 528 | } 529 | 530 | .hbe-input-field-blink:focus + .hbe-input-label-blink, 531 | .input--filled .hbe-input-label-blink { 532 | -webkit-animation: anim-blink-1 0.25s forwards; 533 | animation: anim-blink-1 0.25s forwards; 534 | } 535 | 536 | .hbe-input-field-blink:focus + .hbe-input-label-blink .hbe-input-label-content-blink, 537 | .input--filled .hbe-input-label-content-blink { 538 | -webkit-animation: anim-blink-2 0.25s forwards ease-in; 539 | animation: anim-blink-2 0.25s forwards ease-in; 540 | } 541 | 542 | @-webkit-keyframes anim-blink-1 { 543 | 0%, 70% { 544 | -webkit-transform: translate3d(0, 3em, 0); 545 | transform: translate3d(0, 3em, 0); 546 | } 547 | 71%, 100% { 548 | -webkit-transform: translate3d(0, 0, 0); 549 | transform: translate3d(0, 0, 0); 550 | } 551 | } 552 | 553 | @-webkit-keyframes anim-blink-2 { 554 | 0% { 555 | -webkit-transform: translate3d(0, 0, 0); 556 | transform: translate3d(0, 0, 0); 557 | } 558 | 70%, 71% { 559 | -webkit-transform: translate3d(0, 125%, 0); 560 | transform: translate3d(0, 125%, 0); 561 | opacity: 0; 562 | -webkit-animation-timing-function: ease-out; 563 | } 564 | 100% { 565 | color: transparent; 566 | -webkit-transform: translate3d(0, 200%, 0); 567 | transform: translate3d(0, 200%, 0); 568 | } 569 | } 570 | 571 | @keyframes anim-blink-1 { 572 | 0%, 70% { 573 | -webkit-transform: translate3d(0, 3em, 0); 574 | transform: translate3d(0, 3em, 0); 575 | } 576 | 71%, 100% { 577 | -webkit-transform: translate3d(0, 0, 0); 578 | transform: translate3d(0, 0, 0); 579 | } 580 | } 581 | 582 | @keyframes anim-blink-2 { 583 | 0% { 584 | -webkit-transform: translate3d(0, 0, 0); 585 | transform: translate3d(0, 0, 0); 586 | } 587 | 70%, 71% { 588 | -webkit-transform: translate3d(0, 125%, 0); 589 | transform: translate3d(0, 125%, 0); 590 | opacity: 0; 591 | -webkit-animation-timing-function: ease-out; 592 | } 593 | 100% { 594 | color: transparent; 595 | -webkit-transform: translate3d(0, 200%, 0); 596 | transform: translate3d(0, 200%, 0); 597 | } 598 | } 599 | /* blink theme }}} */ 600 | 601 | /* surge theme {{{ */ 602 | .hbe-input-surge { 603 | overflow: hidden; 604 | padding-bottom: 1em; 605 | } 606 | 607 | .hbe-input-field-surge { 608 | padding: 0.25em 0.5em; 609 | margin-top: 1.25em; 610 | width: 100%; 611 | background: transparent; 612 | color: #D0D0D0; 613 | font-size: 1.55em; 614 | opacity: 0; 615 | } 616 | 617 | .hbe-input-label-surge { 618 | width: 100%; 619 | text-align: left; 620 | position: absolute; 621 | top: 1em; 622 | pointer-events: none; 623 | overflow: hidden; 624 | padding: 0 0.25em; 625 | -webkit-transform: translate3d(1em, 2.75em, 0); 626 | transform: translate3d(1em, 2.75em, 0); 627 | -webkit-transition: -webkit-transform 0.3s; 628 | transition: transform 0.3s; 629 | } 630 | 631 | .hbe-input-label-content-surge { 632 | color: #A4A5A6; 633 | padding: 0.4em 0 0.25em; 634 | -webkit-transition: -webkit-transform 0.3s; 635 | transition: transform 0.3s; 636 | } 637 | 638 | .hbe-input-label-content-surge::after { 639 | content: attr(data-content); 640 | position: absolute; 641 | font-weight: 800; 642 | top: 100%; 643 | left: 0; 644 | height: 100%; 645 | width: 100%; 646 | color: #2C3E50; 647 | padding: 0.25em 0; 648 | letter-spacing: 1px; 649 | font-size: 0.85em; 650 | } 651 | 652 | .hbe-graphic-surge { 653 | fill: #2C3E50; 654 | pointer-events: none; 655 | top: 1em; 656 | bottom: 0px; 657 | height: 4.5em; 658 | z-index: -1; 659 | -webkit-transition: -webkit-transform 0.7s, fill 0.7s; 660 | transition: transform 0.7s, fill 0.7s; 661 | -webkit-transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1); 662 | transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1); 663 | } 664 | 665 | .hbe-input-field-surge:focus, 666 | .input--filled .hbe-input-field-surge { 667 | -webkit-transition: opacity 0s 0.35s; 668 | transition: opacity 0s 0.35s; 669 | opacity: 1; 670 | } 671 | 672 | .hbe-input-field-surge:focus + .hbe-input-label-surge, 673 | .input--filled .hbe-input-label-surge { 674 | -webkit-transition-delay: 0.15s; 675 | transition-delay: 0.15s; 676 | -webkit-transform: translate3d(0, 0, 0); 677 | transform: translate3d(0, 0, 0); 678 | } 679 | 680 | .hbe-input-field-surge:focus + .hbe-input-label-surge .hbe-input-label-content-surge, 681 | .input--filled .hbe-input-label-content-surge { 682 | -webkit-transition-delay: 0.15s; 683 | transition-delay: 0.15s; 684 | -webkit-transform: translate3d(0, -100%, 0); 685 | transform: translate3d(0, -100%, 0); 686 | } 687 | 688 | .hbe-input-field-surge:focus ~ .hbe-graphic-surge, 689 | .input--filled .graphic-surge { 690 | fill: #2C3E50; 691 | -webkit-transform: translate3d(-66.6%, 0, 0); 692 | transform: translate3d(-66.6%, 0, 0); 693 | } 694 | /* surge theme }}} */ 695 | 696 | /* shrink theme {{{ */ 697 | .hbe-input-field-shrink { 698 | width: 100%; 699 | background: transparent; 700 | padding: 0.5em 0; 701 | margin-bottom: 2em; 702 | color: #2C3E50; 703 | } 704 | 705 | .hbe-input-label-shrink { 706 | width: 100%; 707 | position: absolute; 708 | text-align: left; 709 | font-size: 1em; 710 | padding: 10px 0 5px; 711 | pointer-events: none; 712 | } 713 | 714 | .hbe-input-label-shrink::after { 715 | content: ''; 716 | position: absolute; 717 | width: 100%; 718 | height: 7px; 719 | background: #B7C3AC; 720 | left: 0; 721 | top: 100%; 722 | -webkit-transform-origin: 50% 100%; 723 | transform-origin: 50% 100%; 724 | -webkit-transition: -webkit-transform 0.3s, background-color 0.3s; 725 | transition: transform 0.3s, background-color 0.3s; 726 | } 727 | 728 | .hbe-input-label-content-shrink { 729 | padding: 0; 730 | -webkit-transform-origin: 0 0; 731 | transform-origin: 0 0; 732 | -webkit-transition: -webkit-transform 0.3s, color 0.3s; 733 | transition: transform 0.3s, color 0.3s; 734 | } 735 | 736 | .hbe-input-field-shrink:focus + .hbe-input-label-shrink::after, 737 | .input--filled .hbe-input-label-shrink::after { 738 | background: #84AF9B; 739 | -webkit-transform: scale3d(1, 0.25, 1); 740 | transform: scale3d(1, 0.25, 1); 741 | } 742 | 743 | .hbe-input-field-shrink:focus + .hbe-input-label-shrink .hbe-input-label-content-shrink, 744 | .input--filled .hbe-input-label-shrink .hbe-input-label-content-shrink { 745 | color: #84AF9B; 746 | -webkit-transform: translate3d(0, 2em, 0) scale3d(0.655, 0.655, 1); 747 | transform: translate3d(0, 2em, 0) scale3d(0.655, 0.655, 1); 748 | } 749 | /* shrink theme }}} */ 750 | -------------------------------------------------------------------------------- /lib/hbe.surge.html: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 |
7 | 8 | 11 | 12 | 13 | 14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /lib/hbe.up.html: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 |
7 | 8 | 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /lib/hbe.wave.html: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 |
7 | 8 | 11 | 12 | 13 | 14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /lib/hbe.xray.html: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 |
7 | 8 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "name": "D0n9X1n" 4 | }, 5 | "bugs": { 6 | "url": "https://github.com/D0n9X1n/hexo-blog-encrypt/issues" 7 | }, 8 | "description": "Yet, just another blog encrypt plugin for hexo.", 9 | "homepage": "https://github.com/D0n9X1n/hexo-blog-encrypt#readme", 10 | "keywords": [ 11 | "encrypt", 12 | "blog", 13 | "hexo" 14 | ], 15 | "license": "MIT", 16 | "main": "index.js", 17 | "maintainers": [ 18 | { 19 | "email": "D0n9X1n@outlook.com", 20 | "name": "D0n9X1n" 21 | } 22 | ], 23 | "name": "hexo-blog-encrypt", 24 | "repository": { 25 | "type": "git", 26 | "url": "git+ssh://git@github.com/D0n9X1n/hexo-blog-encrypt.git" 27 | }, 28 | "scripts": { 29 | "test": "echo \"Error: no test specified\" && exit 1", 30 | "lint": "eslint --ext .js ./" 31 | }, 32 | "version": "3.1.9", 33 | "devDependencies": { 34 | "eslint": "^6.2.2" 35 | } 36 | } 37 | --------------------------------------------------------------------------------