├── .editorconfig ├── .github ├── FUNDING.yml └── workflows │ ├── deploy.yml │ └── nodejs.yml ├── .gitignore ├── LICENSE ├── README.md ├── _site ├── assets │ ├── orange.jpg │ ├── screenshot-grid.png │ └── screenshot.png ├── favicon.png ├── favicon.svg ├── heti-addon.js ├── heti.css ├── index.css ├── index.html └── scss │ ├── components │ ├── _anchor.scss │ ├── _card.scss │ ├── _grid-container.scss │ └── _panel-list.scss │ ├── heti.scss │ ├── index.scss │ └── lib │ ├── _normalize.scss │ └── _reset.scss ├── js └── heti-addon.js ├── lib ├── _base.scss ├── _font.scss ├── _heading.scss ├── _inline.scss ├── _list.scss ├── _table.scss ├── _variables.scss ├── fonts │ ├── _hei.scss │ ├── _kai.scss │ └── _song.scss ├── helpers │ ├── _add-on.scss │ ├── _block.scss │ └── _inline.scss ├── heti.scss └── modifiers │ ├── ancient.scss │ ├── annotation.scss │ ├── column.scss │ ├── font-stack.scss │ └── writing-mode.scss ├── package-lock.json ├── package.json └── rollup.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig from https://github.com/sivan/dotfiles 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | indent_size = 4 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: heti 2 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build-and-deploy: 10 | runs-on: ubuntu-latest 11 | needs: check-env 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | with: 16 | persist-credentials: false 17 | - name: Setup Node.js 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: 18 21 | - name: NPM Install and Test 22 | run: | 23 | npm install 24 | npm run test 25 | npm run build 26 | - name: Publish 27 | if: needs.check-env.outputs.check-npm == 'true' 28 | uses: JS-DevTools/npm-publish@v1 29 | with: 30 | token: ${{ secrets.NPM_TOKEN }} 31 | - name: Deploy to gh-pages 32 | if: needs.check-env.outputs.check-access == 'true' 33 | uses: JamesIves/github-pages-deploy-action@releases/v3 34 | with: 35 | ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} 36 | BRANCH: gh-pages 37 | FOLDER: _site 38 | check-env: 39 | runs-on: ubuntu-latest 40 | outputs: 41 | check-npm: ${{ steps.check-npm.outputs.defined }} 42 | check-access: ${{ steps.check-access.outputs.defined }} 43 | steps: 44 | - id: check-npm 45 | env: 46 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 47 | if: ${{ env.NPM_TOKEN != '' }} 48 | run: echo "::set-output name=defined::true" 49 | - id: check-access 50 | env: 51 | ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} 52 | if: ${{ env.ACCESS_TOKEN != '' }} 53 | run: echo "::set-output name=defined::true" 54 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Node CI 4 | 5 | on: [push, pull_request] 6 | 7 | jobs: 8 | build: 9 | 10 | runs-on: ubuntu-20.04 11 | 12 | strategy: 13 | matrix: 14 | node-version: [16.x, 18.x] 15 | 16 | steps: 17 | - uses: actions/checkout@v1 18 | - name: Use Node.js ${{ matrix.node-version }} 19 | uses: actions/setup-node@v1 20 | with: 21 | node-version: ${{ matrix.node-version }} 22 | - name: npm install and test 23 | run: | 24 | npm ci 25 | npm test 26 | env: 27 | CI: true 28 | - name: npm build 29 | run: npm run build 30 | env: 31 | CI: true 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Editor generate files 2 | .idea/ 3 | .settings/ 4 | ._project/ 5 | 6 | # Dev dependencies and cache files 7 | node_modules/ 8 | npm-debug.log 9 | 10 | # Folder view configuration files 11 | .DS_Store 12 | Desktop.ini 13 | 14 | # Thumbnail cache files 15 | *~ 16 | ._* 17 | Thumbs.db 18 | 19 | # Files that might appear on external disks 20 | .Spotlight-V100 21 | .Trashes 22 | 23 | # Dist files 24 | dist/ 25 | umd/ 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Sivan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 赫蹏 2 | 3 | 赫蹏(hètí)是专为中文内容展示设计的排版样式增强。它基于通行的中文排版规范而来,可以为网站的读者带来更好的文章阅读体验。 4 | 5 | 预览:[https://sivan.github.io/heti/](https://sivan.github.io/heti/) 6 | 7 | ![Preview](https://raw.githubusercontent.com/sivan/heti/master/_site/assets/screenshot-grid.png) 8 | 9 | 主要特性: 10 | - 贴合网格的排版; 11 | - 全标签样式美化; 12 | - 预置古文、诗词样式; 13 | - 预置多种排版样式(行间注、多栏、竖排等); 14 | - 多种预设字体族(仅限桌面端); 15 | - 简/繁体中文支持; 16 | - 自适应黑暗模式; 17 | - 中西文混排美化,不再手敲空格👏(基于 JavaScript 脚本); 18 | - 全角标点挤压(基于 JavaScript 脚本); 19 | - 兼容 *normalize.css*、*CSS Reset* 等常见样式重置; 20 | - 移动端支持; 21 | - …… 22 | 23 | 总之,用上就会变好看。 24 | 25 | ## 使用方法 26 | 27 | 1. 在页面的 `` 标签中引入 `heti.css` 文件: 28 | ``` 29 | 30 | ``` 31 | 1. 在要作用的容器元素上增加 `class="heti"` 的类名即可: 32 | ``` 33 |
34 |

我的世界观

35 |

有钱人的生活就是这么朴实无华,且枯燥

36 | …… 37 |
38 | ``` 39 | 1. 使用增强脚本(可选): 40 | ``` 41 | 42 | 46 | ``` 47 | 48 | 49 | ## WIP 50 | 51 | 暂时没什么想做的了。 52 | 53 | - [x] 自适应黑暗模式 54 | - [x] 标点挤压 55 | - [x] 中、西文混排 56 | - [x] 繁体中文支持 57 | - [x] 诗词版式 58 | - [x] 行间注版式 59 | 60 | ## Star History 61 | 62 | 谢谢每一位加星的朋友,让这个疫情期间憋在家里无聊做的小项目有了更大的价值。 63 | 64 | [![Star History Chart](https://api.star-history.com/svg?repos=sivan/heti&type=Date)](https://www.star-history.com/#sivan/heti&Date) 65 | 66 | -- EOF -- 67 | -------------------------------------------------------------------------------- /_site/assets/orange.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivan/heti/28f498598f41ad3559b016c0340c63c63cb52d9e/_site/assets/orange.jpg -------------------------------------------------------------------------------- /_site/assets/screenshot-grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivan/heti/28f498598f41ad3559b016c0340c63c63cb52d9e/_site/assets/screenshot-grid.png -------------------------------------------------------------------------------- /_site/assets/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivan/heti/28f498598f41ad3559b016c0340c63c63cb52d9e/_site/assets/screenshot.png -------------------------------------------------------------------------------- /_site/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sivan/heti/28f498598f41ad3559b016c0340c63c63cb52d9e/_site/favicon.png -------------------------------------------------------------------------------- /_site/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /_site/heti-addon.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | (global = global || self, global.Heti = factory()); 5 | }(this, (function () { 'use strict'; 6 | 7 | var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; 8 | 9 | function createCommonjsModule(fn, module) { 10 | return module = { exports: {} }, fn(module, module.exports), module.exports; 11 | } 12 | 13 | var findAndReplaceDOMText = createCommonjsModule(function (module) { 14 | /** 15 | * findAndReplaceDOMText v 0.4.6 16 | * @author James Padolsey http://james.padolsey.com 17 | * @license http://unlicense.org/UNLICENSE 18 | * 19 | * Matches the text of a DOM node against a regular expression 20 | * and replaces each match (or node-separated portions of the match) 21 | * in the specified element. 22 | */ 23 | (function (root, factory) { 24 | if ( module.exports) { 25 | // Node/CommonJS 26 | module.exports = factory(); 27 | } else { 28 | // Browser globals 29 | root.findAndReplaceDOMText = factory(); 30 | } 31 | }(commonjsGlobal, function factory() { 32 | 33 | var PORTION_MODE_RETAIN = 'retain'; 34 | var PORTION_MODE_FIRST = 'first'; 35 | 36 | var doc = document; 37 | var hasOwn = {}.hasOwnProperty; 38 | 39 | function escapeRegExp(s) { 40 | return String(s).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); 41 | } 42 | 43 | function exposed() { 44 | // Try deprecated arg signature first: 45 | return deprecated.apply(null, arguments) || findAndReplaceDOMText.apply(null, arguments); 46 | } 47 | 48 | function deprecated(regex, node, replacement, captureGroup, elFilter) { 49 | if ((node && !node.nodeType) && arguments.length <= 2) { 50 | return false; 51 | } 52 | var isReplacementFunction = typeof replacement == 'function'; 53 | 54 | if (isReplacementFunction) { 55 | replacement = (function(original) { 56 | return function(portion, match) { 57 | return original(portion.text, match.startIndex); 58 | }; 59 | }(replacement)); 60 | } 61 | 62 | // Awkward support for deprecated argument signature (<0.4.0) 63 | var instance = findAndReplaceDOMText(node, { 64 | 65 | find: regex, 66 | 67 | wrap: isReplacementFunction ? null : replacement, 68 | replace: isReplacementFunction ? replacement : '$' + (captureGroup || '&'), 69 | 70 | prepMatch: function(m, mi) { 71 | 72 | // Support captureGroup (a deprecated feature) 73 | 74 | if (!m[0]) throw 'findAndReplaceDOMText cannot handle zero-length matches'; 75 | 76 | if (captureGroup > 0) { 77 | var cg = m[captureGroup]; 78 | m.index += m[0].indexOf(cg); 79 | m[0] = cg; 80 | } 81 | 82 | m.endIndex = m.index + m[0].length; 83 | m.startIndex = m.index; 84 | m.index = mi; 85 | 86 | return m; 87 | }, 88 | filterElements: elFilter 89 | }); 90 | 91 | exposed.revert = function() { 92 | return instance.revert(); 93 | }; 94 | 95 | return true; 96 | } 97 | 98 | /** 99 | * findAndReplaceDOMText 100 | * 101 | * Locates matches and replaces with replacementNode 102 | * 103 | * @param {Node} node Element or Text node to search within 104 | * @param {RegExp} options.find The regular expression to match 105 | * @param {String|Element} [options.wrap] A NodeName, or a Node to clone 106 | * @param {String} [options.wrapClass] A classname to append to the wrapping element 107 | * @param {String|Function} [options.replace='$&'] What to replace each match with 108 | * @param {Function} [options.filterElements] A Function to be called to check whether to 109 | * process an element. (returning true = process element, 110 | * returning false = avoid element) 111 | */ 112 | function findAndReplaceDOMText(node, options) { 113 | return new Finder(node, options); 114 | } 115 | 116 | exposed.NON_PROSE_ELEMENTS = { 117 | br:1, hr:1, 118 | // Media / Source elements: 119 | script:1, style:1, img:1, video:1, audio:1, canvas:1, svg:1, map:1, object:1, 120 | // Input elements 121 | input:1, textarea:1, select:1, option:1, optgroup: 1, button:1 122 | }; 123 | 124 | exposed.NON_CONTIGUOUS_PROSE_ELEMENTS = { 125 | 126 | // Elements that will not contain prose or block elements where we don't 127 | // want prose to be matches across element borders: 128 | 129 | // Block Elements 130 | address:1, article:1, aside:1, blockquote:1, dd:1, div:1, 131 | dl:1, fieldset:1, figcaption:1, figure:1, footer:1, form:1, h1:1, h2:1, h3:1, 132 | h4:1, h5:1, h6:1, header:1, hgroup:1, hr:1, main:1, nav:1, noscript:1, ol:1, 133 | output:1, p:1, pre:1, section:1, ul:1, 134 | // Other misc. elements that are not part of continuous inline prose: 135 | br:1, li: 1, summary: 1, dt:1, details:1, rp:1, rt:1, rtc:1, 136 | // Media / Source elements: 137 | script:1, style:1, img:1, video:1, audio:1, canvas:1, svg:1, map:1, object:1, 138 | // Input elements 139 | input:1, textarea:1, select:1, option:1, optgroup:1, button:1, 140 | // Table related elements: 141 | table:1, tbody:1, thead:1, th:1, tr:1, td:1, caption:1, col:1, tfoot:1, colgroup:1 142 | 143 | }; 144 | 145 | exposed.NON_INLINE_PROSE = function(el) { 146 | return hasOwn.call(exposed.NON_CONTIGUOUS_PROSE_ELEMENTS, el.nodeName.toLowerCase()); 147 | }; 148 | 149 | // Presets accessed via `options.preset` when calling findAndReplaceDOMText(): 150 | exposed.PRESETS = { 151 | prose: { 152 | forceContext: exposed.NON_INLINE_PROSE, 153 | filterElements: function(el) { 154 | return !hasOwn.call(exposed.NON_PROSE_ELEMENTS, el.nodeName.toLowerCase()); 155 | } 156 | } 157 | }; 158 | 159 | exposed.Finder = Finder; 160 | 161 | /** 162 | * Finder -- encapsulates logic to find and replace. 163 | */ 164 | function Finder(node, options) { 165 | 166 | // Add offset to fix browsers which don't support regex lookbehind. 167 | if (!options.offset) { 168 | options.offset = 0; 169 | } 170 | 171 | var preset = options.preset && exposed.PRESETS[options.preset]; 172 | 173 | options.portionMode = options.portionMode || PORTION_MODE_RETAIN; 174 | 175 | if (preset) { 176 | for (var i in preset) { 177 | if (hasOwn.call(preset, i) && !hasOwn.call(options, i)) { 178 | options[i] = preset[i]; 179 | } 180 | } 181 | } 182 | 183 | this.node = node; 184 | this.options = options; 185 | 186 | // Enable match-preparation method to be passed as option: 187 | this.prepMatch = options.prepMatch || this.prepMatch; 188 | 189 | this.reverts = []; 190 | 191 | this.matches = this.search(); 192 | 193 | if (this.matches.length) { 194 | this.processMatches(); 195 | } 196 | 197 | } 198 | 199 | Finder.prototype = { 200 | 201 | /** 202 | * Searches for all matches that comply with the instance's 'match' option 203 | */ 204 | search: function() { 205 | 206 | var match; 207 | var matchIndex = 0; 208 | var offset = 0; 209 | var regex = this.options.find; 210 | var textAggregation = this.getAggregateText(); 211 | var matches = []; 212 | var self = this; 213 | 214 | regex = typeof regex === 'string' ? RegExp(escapeRegExp(regex), 'g') : regex; 215 | 216 | matchAggregation(textAggregation); 217 | 218 | function matchAggregation(textAggregation) { 219 | for (var i = 0, l = textAggregation.length; i < l; ++i) { 220 | 221 | var text = textAggregation[i]; 222 | 223 | if (typeof text !== 'string') { 224 | // Deal with nested contexts: (recursive) 225 | matchAggregation(text); 226 | continue; 227 | } 228 | 229 | if (regex.global) { 230 | while (match = regex.exec(text)) { 231 | matches.push(self.prepMatch(match, matchIndex++, offset)); 232 | } 233 | } else { 234 | if (match = text.match(regex)) { 235 | matches.push(self.prepMatch(match, 0, offset)); 236 | } 237 | } 238 | 239 | offset += text.length; 240 | } 241 | } 242 | 243 | return matches; 244 | 245 | }, 246 | 247 | /** 248 | * Prepares a single match with useful meta info: 249 | */ 250 | prepMatch: function(match, matchIndex, characterOffset) { 251 | 252 | if (!match[0]) { 253 | throw new Error('findAndReplaceDOMText cannot handle zero-length matches'); 254 | } 255 | 256 | match.endIndex = characterOffset + match.index + match[0].length; 257 | match.startIndex = characterOffset + match.index; 258 | match.index = matchIndex; 259 | 260 | return match; 261 | }, 262 | 263 | /** 264 | * Gets aggregate text within subject node 265 | */ 266 | getAggregateText: function() { 267 | 268 | var elementFilter = this.options.filterElements; 269 | var forceContext = this.options.forceContext; 270 | 271 | return getText(this.node); 272 | 273 | /** 274 | * Gets aggregate text of a node without resorting 275 | * to broken innerText/textContent 276 | */ 277 | function getText(node) { 278 | 279 | if (node.nodeType === Node.TEXT_NODE) { 280 | return [node.data]; 281 | } 282 | 283 | if (elementFilter && !elementFilter(node)) { 284 | return []; 285 | } 286 | 287 | var txt = ['']; 288 | var i = 0; 289 | 290 | if (node = node.firstChild) do { 291 | 292 | if (node.nodeType === Node.TEXT_NODE) { 293 | txt[i] += node.data; 294 | continue; 295 | } 296 | 297 | var innerText = getText(node); 298 | 299 | if ( 300 | forceContext && 301 | node.nodeType === Node.ELEMENT_NODE && 302 | (forceContext === true || forceContext(node)) 303 | ) { 304 | txt[++i] = innerText; 305 | txt[++i] = ''; 306 | } else { 307 | if (typeof innerText[0] === 'string') { 308 | // Bridge nested text-node data so that they're 309 | // not considered their own contexts: 310 | // I.e. ['some', ['thing']] -> ['something'] 311 | txt[i] += innerText.shift(); 312 | } 313 | if (innerText.length) { 314 | txt[++i] = innerText; 315 | txt[++i] = ''; 316 | } 317 | } 318 | } while (node = node.nextSibling); 319 | 320 | return txt; 321 | 322 | } 323 | 324 | }, 325 | 326 | /** 327 | * Steps through the target node, looking for matches, and 328 | * calling replaceFn when a match is found. 329 | */ 330 | processMatches: function() { 331 | 332 | var matches = this.matches; 333 | var node = this.node; 334 | var elementFilter = this.options.filterElements; 335 | 336 | var startPortion, 337 | endPortion, 338 | innerPortions = [], 339 | curNode = node, 340 | match = matches.shift(), 341 | atIndex = 0, // i.e. nodeAtIndex 342 | portionIndex = 0, 343 | doAvoidNode, 344 | nodeStack = [node]; 345 | 346 | out: while (true) { 347 | 348 | if (curNode.nodeType === Node.TEXT_NODE) { 349 | 350 | if (!endPortion && curNode.length + atIndex >= match.endIndex) { 351 | // We've found the ending 352 | // (Note that, in the case of a single portion, it'll be an 353 | // endPortion, not a startPortion.) 354 | endPortion = { 355 | node: curNode, 356 | index: portionIndex++, 357 | text: curNode.data.substring(match.startIndex - atIndex + this.options.offset, match.endIndex - atIndex), 358 | 359 | // If it's the first match (atIndex==0) we should just return 0 360 | indexInMatch: atIndex === 0 ? 0 : atIndex - match.startIndex, 361 | 362 | indexInNode: match.startIndex - atIndex + this.options.offset, 363 | endIndexInNode: match.endIndex - atIndex, 364 | isEnd: true 365 | }; 366 | 367 | } else if (startPortion) { 368 | // Intersecting node 369 | innerPortions.push({ 370 | node: curNode, 371 | index: portionIndex++, 372 | text: curNode.data, 373 | indexInMatch: atIndex - match.startIndex, 374 | indexInNode: 0 // always zero for inner-portions 375 | }); 376 | } 377 | 378 | if (!startPortion && curNode.length + atIndex > match.startIndex) { 379 | // We've found the match start 380 | startPortion = { 381 | node: curNode, 382 | index: portionIndex++, 383 | indexInMatch: 0, 384 | indexInNode: match.startIndex - atIndex + this.options.offset, 385 | endIndexInNode: match.endIndex - atIndex, 386 | text: curNode.data.substring(match.startIndex - atIndex + this.options.offset, match.endIndex - atIndex) 387 | }; 388 | } 389 | 390 | atIndex += curNode.data.length; 391 | 392 | } 393 | 394 | doAvoidNode = curNode.nodeType === Node.ELEMENT_NODE && elementFilter && !elementFilter(curNode); 395 | 396 | if (startPortion && endPortion) { 397 | 398 | curNode = this.replaceMatch(match, startPortion, innerPortions, endPortion); 399 | 400 | // processMatches has to return the node that replaced the endNode 401 | // and then we step back so we can continue from the end of the 402 | // match: 403 | 404 | atIndex -= (endPortion.node.data.length - endPortion.endIndexInNode); 405 | 406 | startPortion = null; 407 | endPortion = null; 408 | innerPortions = []; 409 | match = matches.shift(); 410 | portionIndex = 0; 411 | 412 | if (!match) { 413 | break; // no more matches 414 | } 415 | 416 | } else if ( 417 | !doAvoidNode && 418 | (curNode.firstChild || curNode.nextSibling) 419 | ) { 420 | // Move down or forward: 421 | if (curNode.firstChild) { 422 | nodeStack.push(curNode); 423 | curNode = curNode.firstChild; 424 | } else { 425 | curNode = curNode.nextSibling; 426 | } 427 | continue; 428 | } 429 | 430 | // Move forward or up: 431 | while (true) { 432 | if (curNode.nextSibling) { 433 | curNode = curNode.nextSibling; 434 | break; 435 | } 436 | curNode = nodeStack.pop(); 437 | if (curNode === node) { 438 | break out; 439 | } 440 | } 441 | 442 | } 443 | 444 | }, 445 | 446 | /** 447 | * Reverts ... TODO 448 | */ 449 | revert: function() { 450 | // Reversion occurs backwards so as to avoid nodes subsequently 451 | // replaced during the matching phase (a forward process): 452 | for (var l = this.reverts.length; l--;) { 453 | this.reverts[l](); 454 | } 455 | this.reverts = []; 456 | }, 457 | 458 | prepareReplacementString: function(string, portion, match) { 459 | var portionMode = this.options.portionMode; 460 | if ( 461 | portionMode === PORTION_MODE_FIRST && 462 | portion.indexInMatch > 0 463 | ) { 464 | return ''; 465 | } 466 | string = string.replace(/\$(\d+|&|`|')/g, function($0, t) { 467 | var replacement; 468 | switch(t) { 469 | case '&': 470 | replacement = match[0]; 471 | break; 472 | case '`': 473 | replacement = match.input.substring(0, match.startIndex); 474 | break; 475 | case '\'': 476 | replacement = match.input.substring(match.endIndex); 477 | break; 478 | default: 479 | replacement = match[+t] || ''; 480 | } 481 | return replacement; 482 | }); 483 | 484 | if (portionMode === PORTION_MODE_FIRST) { 485 | return string; 486 | } 487 | 488 | if (portion.isEnd) { 489 | return string.substring(portion.indexInMatch); 490 | } 491 | 492 | return string.substring(portion.indexInMatch, portion.indexInMatch + portion.text.length); 493 | }, 494 | 495 | getPortionReplacementNode: function(portion, match) { 496 | 497 | var replacement = this.options.replace || '$&'; 498 | var wrapper = this.options.wrap; 499 | var wrapperClass = this.options.wrapClass; 500 | 501 | if (wrapper && wrapper.nodeType) { 502 | // Wrapper has been provided as a stencil-node for us to clone: 503 | var clone = doc.createElement('div'); 504 | clone.innerHTML = wrapper.outerHTML || new XMLSerializer().serializeToString(wrapper); 505 | wrapper = clone.firstChild; 506 | } 507 | 508 | if (typeof replacement == 'function') { 509 | replacement = replacement(portion, match); 510 | if (replacement && replacement.nodeType) { 511 | return replacement; 512 | } 513 | return doc.createTextNode(String(replacement)); 514 | } 515 | 516 | var el = typeof wrapper == 'string' ? doc.createElement(wrapper) : wrapper; 517 | 518 | if (el && wrapperClass) { 519 | el.className = wrapperClass; 520 | } 521 | 522 | replacement = doc.createTextNode( 523 | this.prepareReplacementString( 524 | replacement, portion, match 525 | ) 526 | ); 527 | 528 | if (!replacement.data) { 529 | return replacement; 530 | } 531 | 532 | if (!el) { 533 | return replacement; 534 | } 535 | 536 | el.appendChild(replacement); 537 | 538 | return el; 539 | }, 540 | 541 | replaceMatch: function(match, startPortion, innerPortions, endPortion) { 542 | 543 | var matchStartNode = startPortion.node; 544 | var matchEndNode = endPortion.node; 545 | 546 | var precedingTextNode; 547 | var followingTextNode; 548 | 549 | if (matchStartNode === matchEndNode) { 550 | 551 | var node = matchStartNode; 552 | 553 | if (startPortion.indexInNode > 0) { 554 | // Add `before` text node (before the match) 555 | precedingTextNode = doc.createTextNode(node.data.substring(0, startPortion.indexInNode)); 556 | node.parentNode.insertBefore(precedingTextNode, node); 557 | } 558 | 559 | // Create the replacement node: 560 | var newNode = this.getPortionReplacementNode( 561 | endPortion, 562 | match 563 | ); 564 | 565 | node.parentNode.insertBefore(newNode, node); 566 | 567 | if (endPortion.endIndexInNode < node.length) { // ????? 568 | // Add `after` text node (after the match) 569 | followingTextNode = doc.createTextNode(node.data.substring(endPortion.endIndexInNode)); 570 | node.parentNode.insertBefore(followingTextNode, node); 571 | } 572 | 573 | node.parentNode.removeChild(node); 574 | 575 | this.reverts.push(function() { 576 | if (precedingTextNode === newNode.previousSibling) { 577 | precedingTextNode.parentNode.removeChild(precedingTextNode); 578 | } 579 | if (followingTextNode === newNode.nextSibling) { 580 | followingTextNode.parentNode.removeChild(followingTextNode); 581 | } 582 | newNode.parentNode.replaceChild(node, newNode); 583 | }); 584 | 585 | return newNode; 586 | 587 | } else { 588 | // Replace matchStartNode -> [innerMatchNodes...] -> matchEndNode (in that order) 589 | 590 | 591 | precedingTextNode = doc.createTextNode( 592 | matchStartNode.data.substring(0, startPortion.indexInNode) 593 | ); 594 | 595 | followingTextNode = doc.createTextNode( 596 | matchEndNode.data.substring(endPortion.endIndexInNode) 597 | ); 598 | 599 | var firstNode = this.getPortionReplacementNode( 600 | startPortion, 601 | match 602 | ); 603 | 604 | var innerNodes = []; 605 | 606 | for (var i = 0, l = innerPortions.length; i < l; ++i) { 607 | var portion = innerPortions[i]; 608 | var innerNode = this.getPortionReplacementNode( 609 | portion, 610 | match 611 | ); 612 | portion.node.parentNode.replaceChild(innerNode, portion.node); 613 | this.reverts.push((function(portion, innerNode) { 614 | return function() { 615 | innerNode.parentNode.replaceChild(portion.node, innerNode); 616 | }; 617 | }(portion, innerNode))); 618 | innerNodes.push(innerNode); 619 | } 620 | 621 | var lastNode = this.getPortionReplacementNode( 622 | endPortion, 623 | match 624 | ); 625 | 626 | matchStartNode.parentNode.insertBefore(precedingTextNode, matchStartNode); 627 | matchStartNode.parentNode.insertBefore(firstNode, matchStartNode); 628 | matchStartNode.parentNode.removeChild(matchStartNode); 629 | 630 | matchEndNode.parentNode.insertBefore(lastNode, matchEndNode); 631 | matchEndNode.parentNode.insertBefore(followingTextNode, matchEndNode); 632 | matchEndNode.parentNode.removeChild(matchEndNode); 633 | 634 | this.reverts.push(function() { 635 | precedingTextNode.parentNode.removeChild(precedingTextNode); 636 | firstNode.parentNode.replaceChild(matchStartNode, firstNode); 637 | followingTextNode.parentNode.removeChild(followingTextNode); 638 | lastNode.parentNode.replaceChild(matchEndNode, lastNode); 639 | }); 640 | 641 | return lastNode; 642 | } 643 | } 644 | 645 | }; 646 | 647 | return exposed; 648 | 649 | })); 650 | }); 651 | 652 | /** 653 | * Heti add-on v 0.1.0 654 | * Add right spacing between CJK & ANS characters 655 | */ 656 | 657 | const hasOwn = {}.hasOwnProperty; 658 | const HETI_NON_CONTIGUOUS_ELEMENTS = Object.assign({}, findAndReplaceDOMText.NON_CONTIGUOUS_PROSE_ELEMENTS, { 659 | ins: 1, del: 1, s: 1, a: 1, 660 | }); 661 | const HETI_SKIPPED_ELEMENTS = Object.assign({}, findAndReplaceDOMText.NON_PROSE_ELEMENTS, { 662 | pre: 1, code: 1, sup: 1, sub: 1, 'heti-spacing': 1, 'heti-close': 1, 663 | }); 664 | const HETI_SKIPPED_CLASS = 'heti-skip'; 665 | 666 | // 部分正则表达式修改自 pangu.js https://github.com/vinta/pangu.js 667 | const CJK = '\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30fa\u30fc-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff'; 668 | const A = 'A-Za-z\u0080-\u00ff\u0370-\u03ff'; 669 | const N = '0-9'; 670 | const S = '`~!@#\\$%\\^&\\*\\(\\)-_=\\+\\[\\]{}\\\\\\|;:\'",<.>\\/\\?'; 671 | const ANS = `${A}${N}${S}`; 672 | const REG_CJK_FULL = `(?<=[${CJK}])( *[${ANS}]+(?: +[${ANS}]+)* *)(?=[${CJK}])`; 673 | const REG_CJK_START = `([${ANS}]+(?: +[${ANS}]+)* *)(?=[${CJK}])`; 674 | const REG_CJK_END = `(?<=[${CJK}])( *[${ANS}]+(?: +[${ANS}]+)*)`; 675 | const REG_CJK_FULL_WITHOUT_LOOKBEHIND = `(?:[${CJK}])( *[${ANS}]+(?: +[${ANS}]+)* *)(?=[${CJK}])`; 676 | const REG_CJK_END_WITHOUT_LOOKBEHIND = `(?:[${CJK}])( *[${ANS}]+(?: +[${ANS}]+)*)`; 677 | const REG_BD_STOP = `。.,、:;!‼?⁇`; 678 | const REG_BD_SEP = `·・‧`; 679 | const REG_BD_OPEN = `「『(《〈【〖〔[{`; 680 | const REG_BD_CLOSE = `」』)》〉】〗〕]}`; 681 | const REG_BD_START = `${REG_BD_OPEN}${REG_BD_CLOSE}`; 682 | const REG_BD_END = `${REG_BD_STOP}${REG_BD_OPEN}${REG_BD_CLOSE}`; 683 | const REG_BD_HALF_OPEN = `“‘`; 684 | const REG_BD_HALF_CLOSE = `”’`; 685 | const REG_BD_HALF_START = `${REG_BD_HALF_OPEN}${REG_BD_HALF_CLOSE}`; 686 | 687 | class Heti { 688 | constructor (rootSelector) { 689 | let supportLookBehind = true; 690 | 691 | try { 692 | new RegExp(`(?<=\d)\d`, 'g').test(''); 693 | } catch (err) { 694 | console.info(err.name, '该浏览器尚未实现 RegExp positive lookbehind'); 695 | supportLookBehind = false; 696 | } 697 | 698 | this.rootSelector = rootSelector || '.heti'; 699 | this.REG_FULL = new RegExp(supportLookBehind ? REG_CJK_FULL : REG_CJK_FULL_WITHOUT_LOOKBEHIND, 'g'); 700 | this.REG_START = new RegExp(REG_CJK_START, 'g'); 701 | this.REG_END = new RegExp(supportLookBehind ? REG_CJK_END : REG_CJK_END_WITHOUT_LOOKBEHIND, 'g'); 702 | this.offsetWidth = supportLookBehind ? 0 : 1; 703 | this.funcForceContext = function forceContext (el) { 704 | return hasOwn.call(HETI_NON_CONTIGUOUS_ELEMENTS, el.nodeName.toLowerCase()) 705 | }; 706 | this.funcFilterElements = function filterElements (el) { 707 | return ( 708 | !(el.classList && el.classList.contains(HETI_SKIPPED_CLASS)) && 709 | !hasOwn.call(HETI_SKIPPED_ELEMENTS, el.nodeName.toLowerCase()) 710 | ) 711 | }; 712 | } 713 | 714 | spacingElements (elmList) { 715 | for (let $$root of elmList) { 716 | this.spacingElement($$root); 717 | } 718 | } 719 | 720 | spacingElement ($$elm) { 721 | const commonConfig = { 722 | forceContext: this.funcForceContext, 723 | filterElements: this.funcFilterElements, 724 | }; 725 | const getWrapper = function (elementName, classList, text) { 726 | const $$r = document.createElement(elementName); 727 | $$r.className = classList; 728 | $$r.textContent = text.trim(); 729 | return $$r 730 | }; 731 | 732 | findAndReplaceDOMText($$elm, Object.assign({}, commonConfig, { 733 | find: this.REG_FULL, 734 | replace: portion => getWrapper('heti-spacing', 'heti-spacing-start heti-spacing-end', portion.text), 735 | offset: this.offsetWidth, 736 | })); 737 | 738 | findAndReplaceDOMText($$elm, Object.assign({}, commonConfig, { 739 | find: this.REG_START, 740 | replace: portion => getWrapper('heti-spacing', 'heti-spacing-start', portion.text), 741 | })); 742 | 743 | findAndReplaceDOMText($$elm, Object.assign({}, commonConfig, { 744 | find: this.REG_END, 745 | replace: portion => getWrapper('heti-spacing', 'heti-spacing-end', portion.text), 746 | offset: this.offsetWidth, 747 | })); 748 | 749 | findAndReplaceDOMText($$elm, Object.assign({}, commonConfig, { 750 | find: new RegExp(`([${REG_BD_STOP}])(?=[${REG_BD_START}])|([${REG_BD_OPEN}])(?=[${REG_BD_OPEN}])|([${REG_BD_CLOSE}])(?=[${REG_BD_END}])`,'g'), 751 | replace: portion => getWrapper('heti-adjacent', 'heti-adjacent-half', portion.text), 752 | offset: this.offsetWidth, 753 | })); 754 | 755 | findAndReplaceDOMText($$elm, Object.assign({}, commonConfig, { 756 | find: new RegExp(`([${REG_BD_SEP}])(?=[${REG_BD_OPEN}])|([${REG_BD_CLOSE}])(?=[${REG_BD_SEP}])`,'g'), 757 | replace: portion => getWrapper('heti-adjacent', 'heti-adjacent-quarter', portion.text), 758 | offset: this.offsetWidth, 759 | })); 760 | 761 | // 使用弯引号的情况下,在停顿符号接弯引号(如「。“」)或弯引号接全角开引号(如“《」)时,间距缩进调整到四分之一 762 | findAndReplaceDOMText($$elm, Object.assign({}, commonConfig, { 763 | find: new RegExp(`([${REG_BD_STOP}])(?=[${REG_BD_HALF_START}])|([${REG_BD_HALF_OPEN}])(?=[${REG_BD_OPEN}])`,'g'), 764 | replace: portion => getWrapper('heti-adjacent', 'heti-adjacent-quarter', portion.text), 765 | offset: this.offsetWidth, 766 | })); 767 | } 768 | 769 | autoSpacing () { 770 | const callback = () => { 771 | const $$rootList = document.querySelectorAll(this.rootSelector); 772 | 773 | for (let $$root of $$rootList) { 774 | this.spacingElement($$root); 775 | } 776 | }; 777 | if (document.readyState === 'complete') setTimeout(callback); 778 | else document.addEventListener('DOMContentLoaded', callback); 779 | } 780 | } 781 | 782 | return Heti; 783 | 784 | }))); 785 | -------------------------------------------------------------------------------- /_site/heti.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /*! 3 | * Project: Heti 4 | * URL: https://github.com/sivan/heti 5 | * Author: Sivan [sun.sivan@gmail.com] 6 | */ 7 | @font-face { 8 | font-family: "Heti Hei"; 9 | src: "Heti Hei SC", "Heti Hei TC", "Heti Hei JP", "Heti Hei KR"; 10 | } 11 | @font-face { 12 | font-family: "Heti Hei SC"; 13 | src: local("PingFang SC Regular"), local("Heiti SC Regular"), local("Microsoft YaHei"), local("Source Han Sans CN Regular"), local("Noto Sans CJK SC Regular"), local("WenQuanYi Micro Hei"), local("Droid Sans Fallback"); 14 | } 15 | @font-face { 16 | font-family: "Heti Hei TC"; 17 | src: local("PingFang TC Regular"), local("Heiti TC Regular"), local("Microsoft Jhenghei"), local("Source Han Sans HK Regular"), local("Source Han Sans TW Regular"), local("Noto Sans CJK TC Regular"), local("WenQuanYi Micro Hei"), local("Droid Sans Fallback"); 18 | } 19 | @font-face { 20 | font-family: "Heti Hei JP"; 21 | src: local("Hiragino Sans GB W3"), local("Source Han Sans JP Regular"), local("Noto Sans CJK JP Regular"), local("Droid Sans Fallback"); 22 | } 23 | @font-face { 24 | font-family: "Heti Hei KR"; 25 | src: local("Source Han Sans KR Regular"), local("Noto Sans CJK KR Regular"), local("Droid Sans Fallback"); 26 | } 27 | @font-face { 28 | font-family: "Heti Hei"; 29 | font-weight: 200; 30 | src: "Heti Hei SC Light", "Heti Hei TC Light", "Heti Hei JP Light", "Heti Hei KR Light"; 31 | } 32 | @font-face { 33 | font-family: "Heti Hei SC Light"; 34 | font-weight: 200; 35 | src: local("PingFang SC Light"), local("Heiti SC Light"), "Heti Hei SC Light Fallback", local("Source Han Sans CN Light"), local("Noto Sans CJK SC Light"); 36 | } 37 | @font-face { 38 | font-family: "Heti Hei TC Light"; 39 | font-weight: 200; 40 | src: local("PingFang TC Light"), local("Heiti TC Light"), local("Microsoft Jhenghei Light"), local("Source Han Sans HK Light"), local("Source Han Sans TW Light"), local("Noto Sans CJK TC Light"); 41 | } 42 | @font-face { 43 | font-family: "Heti Hei JP Light"; 44 | font-weight: 200; 45 | src: local("Source Han Sans JP Light"), local("Noto Sans CJK JP Light"); 46 | } 47 | @font-face { 48 | font-family: "Heti Hei KR Light"; 49 | font-weight: 200; 50 | src: local("Source Han Sans KR Light"), local("Noto Sans CJK KR Light"); 51 | } 52 | @font-face { 53 | font-family: "Heti Hei SC Light Fallback"; 54 | font-weight: 200; 55 | src: local("Microsoft YaHei"), local("Droid Sans Fallback"); 56 | } 57 | @font-face { 58 | font-family: "Heti Hei"; 59 | font-weight: 600; 60 | src: "Heti Hei SC Bold", "Heti Hei TC Bold", "Heti Hei JP Bold", "Heti Hei KR Bold"; 61 | } 62 | @font-face { 63 | font-family: "Heti Hei SC Bold"; 64 | font-weight: 600; 65 | src: local("PingFang SC Medium"), local("Heiti SC Medium"), "Heti Hei SC Bold Fallback", local("Source Han Sans CN Bold"), local("Noto Sans CJK SC Bold"); 66 | } 67 | @font-face { 68 | font-family: "Heti Hei TC Bold"; 69 | font-weight: 600; 70 | src: local("PingFang TC Medium"), local("Heiti TC Medium"), local("Microsoft Jhenghei Bold"), local("Source Han Sans HK Bold"), local("Source Han Sans TW Bold"), local("Noto Sans CJK TC Bold"); 71 | } 72 | @font-face { 73 | font-family: "Heti Hei JP Bold"; 74 | font-weight: 600; 75 | src: local("Hiragino Sans GB W6"), local("Source Han Sans JP Bold"), local("Noto Sans CJK JP Bold"); 76 | } 77 | @font-face { 78 | font-family: "Heti Hei KR Bold"; 79 | font-weight: 600; 80 | src: local("Source Han Sans KR Bold"), local("Noto Sans CJK KR Bold"); 81 | } 82 | @font-face { 83 | font-family: "Heti Hei SC Bold Fallback"; 84 | font-weight: 600; 85 | src: local("Microsoft YaHei"), local("Droid Sans Fallback"); 86 | } 87 | @font-face { 88 | font-family: "Heti Hei Black"; 89 | font-weight: 800; 90 | src: "Heti Hei SC Black", "Heti Hei TC Black", "Heti Hei JP Black", "Heti Hei KR Black"; 91 | } 92 | @font-face { 93 | font-family: "Heti Hei SC Black"; 94 | font-weight: 800; 95 | src: local("Lantinghei SC Heavy"), local("PingFang SC Semibold"), local("Heiti SC Medium"), "Heti Hei SC Black Fallback", local("Source Han Sans CN Heavy"), local("Noto Sans CJK SC Heavy"); 96 | } 97 | @font-face { 98 | font-family: "Heti Hei TC Black"; 99 | font-weight: 800; 100 | src: local("Lantinghei TC Heavy"), local("PingFang TC Semibold"), local("Heiti TC Medium"), local("Microsoft Jhenghei Bold"), local("Source Han Sans HK Heavy"), local("Source Han Sans TW Heavy"), local("Noto Sans CJK TC Heavy"); 101 | } 102 | @font-face { 103 | font-family: "Heti Hei JP Black"; 104 | font-weight: 800; 105 | src: local("Hiragino Sans GB W6"), local("Source Han Sans JP Heavy"), local("Noto Sans CJK JP Heavy"); 106 | } 107 | @font-face { 108 | font-family: "Heti Hei KR Black"; 109 | font-weight: 800; 110 | src: local("Source Han Sans KR Heavy"), local("Noto Sans CJK KR Heavy"); 111 | } 112 | @font-face { 113 | font-family: "Heti Hei SC Black Fallback"; 114 | font-weight: 800; 115 | src: local("Microsoft YaHei"), local("Droid Sans Fallback"); 116 | } 117 | @font-face { 118 | font-family: "Heti Song"; 119 | src: local("Songti SC Regular"), local("Songti TC Regular"), local("SimSun"); 120 | } 121 | @font-face { 122 | font-family: "Heti Song"; 123 | font-weight: 200; 124 | src: local("Songti SC Light"), local("Songti TC Light"), "Heti Song Light Fallback"; 125 | } 126 | @font-face { 127 | font-family: "Heti Song Light Fallback"; 128 | font-weight: 200; 129 | src: local("SimSun"); 130 | } 131 | @font-face { 132 | font-family: "Heti Song"; 133 | font-weight: 600; 134 | src: local("Songti SC Bold"), local("Songti TC Bold"), "Heti Song Bold Fallback"; 135 | } 136 | @font-face { 137 | font-family: "Heti Song Bold Fallback"; 138 | font-weight: 600; 139 | src: local("SimSun"); 140 | } 141 | @font-face { 142 | font-family: "Heti Song Black"; 143 | font-weight: 800; 144 | src: local("Songti SC Black"), local("SimSun"); 145 | } 146 | @font-face { 147 | font-family: "Heti Kai"; 148 | src: local("Kaiti SC Regular"), local("Kaiti TC Regular"), local("STKaiti"), local("Kaiti"), local("BiauKai"); 149 | } 150 | @font-face { 151 | font-family: "Heti Kai"; 152 | font-weight: 600; 153 | src: local("Kaiti SC Bold"), local("Kaiti TC Bold"); 154 | } 155 | @font-face { 156 | font-family: "Heti Kai Bold Fallback"; 157 | font-weight: 600; 158 | src: local("STKaiti"), local("Kaiti") local("BiauKai"); 159 | } 160 | @font-face { 161 | font-family: "Heti Kai Black"; 162 | font-weight: 800; 163 | src: local("Kaiti SC Black"), local("Kaiti TC Black"), local("STKaiti"), local("Kaiti"); 164 | } 165 | .heti { 166 | max-width: 42em; 167 | font-size: 16px; 168 | font-weight: 400; 169 | -webkit-font-smoothing: subpixel-antialiased; 170 | line-height: 1.5; 171 | overflow-wrap: break-word; 172 | word-wrap: break-word; 173 | hyphens: auto; 174 | letter-spacing: 0.02em; 175 | } 176 | .heti::before, .heti::after { 177 | content: ""; 178 | display: table; 179 | } 180 | .heti::after { 181 | clear: both; 182 | } 183 | .heti > *:first-child, 184 | .heti section > *:first-child, 185 | .heti td > *:first-child { 186 | margin-block-start: 0 !important; 187 | } 188 | .heti > *:last-child, 189 | .heti section > *:last-child, 190 | .heti td > *:last-child { 191 | margin-block-end: 0 !important; 192 | } 193 | .heti blockquote { 194 | margin-block-start: 12px; 195 | margin-block-end: 24px; 196 | margin-inline-start: 32px; 197 | margin-inline-end: 32px; 198 | padding-block-start: 12px; 199 | padding-block-end: 12px; 200 | padding-inline-start: 16px; 201 | padding-inline-end: 16px; 202 | background-color: hsla(0deg, 0%, 0%, 0.054); 203 | } 204 | [data-darkmode=dark] .heti blockquote { 205 | background-color: hsla(0deg, 0%, 100%, 0.054); 206 | } 207 | @media (prefers-color-scheme: dark) { 208 | [data-darkmode=auto] .heti blockquote { 209 | background-color: hsla(0deg, 0%, 100%, 0.054); 210 | } 211 | } 212 | .heti figure { 213 | display: block; 214 | text-align: center; 215 | } 216 | .heti figure > img { 217 | display: block; 218 | margin-inline-start: auto; 219 | margin-inline-end: auto; 220 | } 221 | .heti hr { 222 | width: 30%; 223 | height: 1px; 224 | margin-block-start: 48px; 225 | margin-block-end: 47px; 226 | margin-inline-start: auto; 227 | margin-inline-end: auto; 228 | border: 0; 229 | background-color: hsl(0deg, 0%, 80%); 230 | } 231 | [data-darkmode=dark] .heti hr { 232 | background-color: hsl(0deg, 0%, 25%); 233 | } 234 | @media (prefers-color-scheme: dark) { 235 | [data-darkmode=auto] .heti hr { 236 | background-color: hsl(0deg, 0%, 25%); 237 | } 238 | } 239 | .heti p { 240 | margin-block-start: 12px; 241 | margin-block-end: 24px; 242 | text-align: justify; 243 | } 244 | .heti p:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)), .heti p:not(:lang(zh)) { 245 | text-align: start; 246 | } 247 | .heti pre { 248 | margin-block-start: 12px; 249 | margin-block-end: 12px; 250 | margin-inline-start: 0; 251 | margin-inline-end: 0; 252 | padding-block-start: 12px; 253 | padding-block-end: 12px; 254 | padding-inline-start: 16px; 255 | padding-inline-end: 16px; 256 | overflow: auto; 257 | font-family: "SFMono-Regular", consolas, "Liberation Mono", menlo, courier, monospace, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 258 | white-space: pre; 259 | word-wrap: normal; 260 | border-radius: 4px; 261 | background-color: hsla(0deg, 0%, 0%, 0.054); 262 | } 263 | [data-darkmode=dark] .heti pre { 264 | background-color: hsla(0deg, 0%, 100%, 0.054); 265 | } 266 | @media (prefers-color-scheme: dark) { 267 | [data-darkmode=auto] .heti pre { 268 | background-color: hsla(0deg, 0%, 100%, 0.054); 269 | } 270 | } 271 | .heti pre code { 272 | margin: 0; 273 | padding: 0; 274 | border: 0; 275 | border-radius: 0; 276 | background-color: transparent; 277 | color: inherit; 278 | } 279 | .heti:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)), .heti:not(:lang(zh)) { 280 | letter-spacing: 0; 281 | } 282 | .heti a, 283 | .heti abbr, 284 | .heti code, 285 | .heti heti-spacing, 286 | .heti [lang=en-US] { 287 | letter-spacing: normal; 288 | } 289 | .heti h1, 290 | .heti h2, 291 | .heti h3, 292 | .heti h4, 293 | .heti h5, 294 | .heti h6 { 295 | position: relative; 296 | margin: 0; 297 | margin-block-start: 24px; 298 | margin-block-end: 12px; 299 | font-weight: 600; 300 | } 301 | .heti h1 { 302 | margin-block-end: 24px; 303 | font-size: 32px; 304 | line-height: 48px; 305 | } 306 | .heti h2 { 307 | font-size: 24px; 308 | line-height: 36px; 309 | } 310 | .heti h3 { 311 | font-size: 20px; 312 | line-height: 36px; 313 | } 314 | .heti h4 { 315 | font-size: 18px; 316 | line-height: 24px; 317 | } 318 | .heti h5 { 319 | font-size: 16px; 320 | line-height: 24px; 321 | } 322 | .heti h6 { 323 | font-size: 14px; 324 | line-height: 24px; 325 | } 326 | .heti h1, 327 | .heti h2, 328 | .heti h3 { 329 | letter-spacing: 0.05em; 330 | } 331 | .heti h1:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)), .heti h1:not(:lang(zh)), 332 | .heti h2:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)), 333 | .heti h2:not(:lang(zh)), 334 | .heti h3:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)), 335 | .heti h3:not(:lang(zh)) { 336 | letter-spacing: 0; 337 | } 338 | .heti h1 + h2, 339 | .heti h2 + h3, 340 | .heti h3 + h4, 341 | .heti h4 + h5, 342 | .heti h5 + h6 { 343 | margin-block-start: 12px; 344 | } 345 | .heti ul, 346 | .heti ol, 347 | .heti dl { 348 | margin-block-start: 12px; 349 | margin-block-end: 24px; 350 | } 351 | .heti ul, 352 | .heti ol { 353 | padding-inline-start: 32px; 354 | } 355 | .heti ul ul, 356 | .heti ul ol, 357 | .heti ol ul, 358 | .heti ol ol { 359 | margin-block-start: 0; 360 | margin-block-end: 0; 361 | } 362 | .heti ul { 363 | list-style-type: disc; 364 | } 365 | .heti ol { 366 | list-style-type: decimal; 367 | } 368 | .heti ul ul, 369 | .heti ol ul { 370 | list-style-type: circle; 371 | } 372 | .heti ul ul ul, 373 | .heti ul ol ul, 374 | .heti ol ul ul, 375 | .heti ol ol ul { 376 | list-style-type: square; 377 | } 378 | .heti li { 379 | list-style-type: unset; 380 | } 381 | .heti table { 382 | box-sizing: border-box; 383 | table-layout: fixed; 384 | margin-block-start: 12px; 385 | margin-block-end: 24px; 386 | margin-inline-start: auto; 387 | margin-inline-end: auto; 388 | border-collapse: collapse; 389 | border-width: 1px; 390 | border-style: solid; 391 | border-color: hsl(0deg, 0%, 80%); 392 | word-break: break-word; 393 | } 394 | [data-darkmode=dark] .heti table { 395 | border-color: hsl(0deg, 0%, 25%); 396 | } 397 | @media (prefers-color-scheme: dark) { 398 | [data-darkmode=auto] .heti table { 399 | border-color: hsl(0deg, 0%, 25%); 400 | } 401 | } 402 | .heti th, 403 | .heti td { 404 | padding-block-start: 6px; 405 | padding-block-end: 6px; 406 | padding-inline-start: 8px; 407 | padding-inline-end: 8px; 408 | border-width: 1px; 409 | border-style: solid; 410 | border-color: hsl(0deg, 0%, 80%); 411 | } 412 | [data-darkmode=dark] .heti th, 413 | [data-darkmode=dark] .heti td { 414 | border-color: hsl(0deg, 0%, 25%); 415 | } 416 | @media (prefers-color-scheme: dark) { 417 | [data-darkmode=auto] .heti th, 418 | [data-darkmode=auto] .heti td { 419 | border-color: hsl(0deg, 0%, 25%); 420 | } 421 | } 422 | .heti caption { 423 | caption-side: bottom; 424 | margin-block-start: 2px; 425 | margin-block-end: -4px; 426 | font-size: 14px; 427 | line-height: 24px; 428 | } 429 | .heti a { 430 | text-decoration: none; 431 | } 432 | .heti a:hover { 433 | padding-block-end: 1px; 434 | border-block-end: 1px solid currentColor; 435 | text-decoration: none; 436 | } 437 | .heti abbr[title] { 438 | padding-block-end: 1px; 439 | border-block-end: 1px dotted; 440 | text-decoration: none; 441 | cursor: help; 442 | } 443 | .heti b, 444 | .heti strong { 445 | font-weight: 600; 446 | } 447 | .heti code { 448 | margin-inline-start: 0.25em; 449 | margin-inline-end: 0.25em; 450 | font-family: "SFMono-Regular", consolas, "Liberation Mono", menlo, courier, monospace, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 451 | font-size: 0.875em; 452 | } 453 | .heti dfn { 454 | font-weight: 600; 455 | } 456 | .heti dfn:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)), .heti dfn:not(:lang(zh)) { 457 | font-weight: 400; 458 | } 459 | .heti em { 460 | font-weight: 600; 461 | } 462 | .heti figcaption { 463 | display: inline-block; 464 | vertical-align: top; 465 | font-size: 14px; 466 | text-align: start; 467 | } 468 | .heti i { 469 | font-style: italic; 470 | } 471 | .heti ins, 472 | .heti u { 473 | padding-block-end: 1px; 474 | border-block-end: 1px solid; 475 | text-decoration: none; 476 | } 477 | .heti mark { 478 | padding-block-start: 2px; 479 | padding-block-end: 2px; 480 | padding-inline-start: 1px; 481 | padding-inline-end: 1px; 482 | margin-inline-start: 1px; 483 | margin-inline-end: 1px; 484 | background-color: hsla(58deg, 100%, 50%, 0.88); 485 | color: inherit; 486 | } 487 | [data-darkmode=dark] .heti mark { 488 | background-color: hsla(58deg, 100%, 15%, 0.88); 489 | } 490 | @media (prefers-color-scheme: dark) { 491 | [data-darkmode=auto] .heti mark { 492 | background-color: hsla(58deg, 100%, 15%, 0.88); 493 | } 494 | } 495 | .heti q { 496 | quotes: "「" "」" "『" "』"; 497 | } 498 | .heti q:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)), .heti q:not(:lang(zh)) { 499 | quotes: initial; 500 | quotes: auto; 501 | } 502 | .heti rt { 503 | font-size: 0.875em; 504 | font-weight: 400; 505 | } 506 | .heti small { 507 | font-size: 0.875em; 508 | } 509 | .heti strong { 510 | font-weight: 600; 511 | } 512 | .heti sub, 513 | .heti sup { 514 | position: relative; 515 | margin-inline-start: 0.25em; 516 | margin-inline-end: 0.25em; 517 | font-size: 0.75em; 518 | font-family: "Helvetica Neue", helvetica, arial, "Heti Hei", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 519 | font-style: normal; 520 | line-height: 1; 521 | vertical-align: baseline; 522 | } 523 | .heti sub { 524 | bottom: -0.25em; 525 | } 526 | .heti sup { 527 | top: -0.5em; 528 | } 529 | .heti sup:target, 530 | .heti sup a:target { 531 | background-color: hsl(210deg, 100%, 93%); 532 | } 533 | [data-darkmode=dark] .heti sup:target, 534 | [data-darkmode=dark] .heti sup a:target { 535 | background-color: hsl(210deg, 40%, 38%); 536 | } 537 | @media (prefers-color-scheme: dark) { 538 | [data-darkmode=auto] .heti sup:target, 539 | [data-darkmode=auto] .heti sup a:target { 540 | background-color: hsl(210deg, 40%, 38%); 541 | } 542 | } 543 | .heti summary { 544 | padding-inline-start: 1em; 545 | outline: 0; 546 | cursor: pointer; 547 | } 548 | .heti summary::-webkit-details-marker { 549 | width: 0.6em; 550 | margin-inline-end: 0.4em; 551 | } 552 | .heti u[title] { 553 | cursor: help; 554 | border-block-end-width: 3px; 555 | border-block-end-style: double; 556 | border-block-end-color: hsla(0deg, 0%, 0%, 0.54); 557 | } 558 | [data-darkmode=dark] .heti u[title] { 559 | border-block-end-color: hsla(0deg, 0%, 100%, 0.54); 560 | } 561 | @media (prefers-color-scheme: dark) { 562 | [data-darkmode=auto] .heti u[title] { 563 | border-block-end-color: hsla(0deg, 0%, 100%, 0.54); 564 | } 565 | } 566 | .heti address, 567 | .heti cite, 568 | .heti dfn, 569 | .heti dt, 570 | .heti em { 571 | font-style: normal; 572 | } 573 | .heti address:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)), .heti address:not(:lang(zh)), 574 | .heti cite:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)), 575 | .heti cite:not(:lang(zh)), 576 | .heti dfn:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)), 577 | .heti dfn:not(:lang(zh)), 578 | .heti dt:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)), 579 | .heti dt:not(:lang(zh)), 580 | .heti em:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)), 581 | .heti em:not(:lang(zh)) { 582 | font-style: italic; 583 | } 584 | .heti abbr[title], 585 | .heti del, 586 | .heti ins, 587 | .heti s, 588 | .heti u { 589 | margin-inline-start: 1px; 590 | margin-inline-end: 1px; 591 | } 592 | .heti, .heti--sans { 593 | font-family: "Helvetica Neue", helvetica, arial, "Heti Hei", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 594 | } 595 | .heti--serif { 596 | font-family: "Times New Roman", times, "Heti Song", serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 597 | } 598 | .heti--classic { 599 | font-family: "Times New Roman", times, "Heti Song", serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 600 | } 601 | .heti--classic h1, 602 | .heti--classic h2, 603 | .heti--classic h3, 604 | .heti--classic h4, 605 | .heti--classic h5, 606 | .heti--classic h6 { 607 | font-family: "Times New Roman", times, "Heti Kai Black", serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 608 | font-weight: 800; 609 | } 610 | .heti--classic blockquote, 611 | .heti--classic cite, 612 | .heti--classic q { 613 | font-family: "Times New Roman", times, "Heti Kai", serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 614 | } 615 | .heti--classic figcaption, 616 | .heti--classic caption, 617 | .heti--classic th { 618 | font-family: "Helvetica Neue", helvetica, arial, "Heti Hei", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 619 | } 620 | .heti--hei { 621 | font-family: "Helvetica Neue", helvetica, arial, "Heti Hei", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 622 | } 623 | .heti--song { 624 | font-family: "Times New Roman", times, "Heti Song", serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 625 | } 626 | .heti--kai { 627 | font-family: "Times New Roman", times, "Heti Kai", serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 628 | } 629 | .heti--columns-1, .heti--columns-2, .heti--columns-3, .heti--columns-4, .heti--columns-16em, .heti--columns-20em, .heti--columns-24em, .heti--columns-28em, .heti--columns-32em, .heti--columns-36em, .heti--columns-40em, .heti--columns-44em, .heti--columns-48em, .heti comma { 630 | max-width: none; 631 | column-gap: 2em; 632 | } 633 | .heti--columns-1 p, .heti--columns-2 p, .heti--columns-3 p, .heti--columns-4 p, .heti--columns-16em p, .heti--columns-20em p, .heti--columns-24em p, .heti--columns-28em p, .heti--columns-32em p, .heti--columns-36em p, .heti--columns-40em p, .heti--columns-44em p, .heti--columns-48em p, .heti comma p { 634 | margin-block-start: 6px; 635 | margin-block-end: 12px; 636 | text-indent: 2em; 637 | } 638 | .heti--columns-1 { 639 | column-count: 1; 640 | } 641 | .heti--columns-2 { 642 | column-count: 2; 643 | } 644 | .heti--columns-3 { 645 | column-count: 3; 646 | } 647 | .heti--columns-4 { 648 | column-count: 4; 649 | } 650 | .heti--columns-16em { 651 | column-width: 16em; 652 | } 653 | .heti--columns-20em { 654 | column-width: 20em; 655 | } 656 | .heti--columns-24em { 657 | column-width: 24em; 658 | } 659 | .heti--columns-28em { 660 | column-width: 28em; 661 | } 662 | .heti--columns-32em { 663 | column-width: 32em; 664 | } 665 | .heti--columns-36em { 666 | column-width: 36em; 667 | } 668 | .heti--columns-40em { 669 | column-width: 40em; 670 | } 671 | .heti--columns-44em { 672 | column-width: 44em; 673 | } 674 | .heti--columns-48em { 675 | column-width: 48em; 676 | } 677 | .heti--vertical { 678 | max-width: none; 679 | max-height: 42em; 680 | writing-mode: vertical-rl; 681 | letter-spacing: 0.125em; 682 | } 683 | .heti--vertical h1, 684 | .heti--vertical h2, 685 | .heti--vertical h3, 686 | .heti--vertical h4, 687 | .heti--vertical h5, 688 | .heti--vertical h6 { 689 | text-align: start; 690 | } 691 | .heti--vertical q { 692 | quotes: "「" "」" "『" "』"; 693 | } 694 | .heti--ancient, .heti--poetry { 695 | font-family: "Times New Roman", times, "Heti Song", serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 696 | } 697 | .heti--ancient h1, 698 | .heti--ancient h2, 699 | .heti--ancient h3, 700 | .heti--ancient h4, 701 | .heti--ancient h5, 702 | .heti--ancient h6, .heti--poetry h1, 703 | .heti--poetry h2, 704 | .heti--poetry h3, 705 | .heti--poetry h4, 706 | .heti--poetry h5, 707 | .heti--poetry h6 { 708 | font-family: "Times New Roman", times, "Heti Kai Black", serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 709 | font-weight: 800; 710 | text-align: center; 711 | } 712 | .heti--ancient h1 .heti-meta, 713 | .heti--ancient h2 .heti-meta, 714 | .heti--ancient h3 .heti-meta, 715 | .heti--ancient h4 .heti-meta, 716 | .heti--ancient h5 .heti-meta, 717 | .heti--ancient h6 .heti-meta, .heti--poetry h1 .heti-meta, 718 | .heti--poetry h2 .heti-meta, 719 | .heti--poetry h3 .heti-meta, 720 | .heti--poetry h4 .heti-meta, 721 | .heti--poetry h5 .heti-meta, 722 | .heti--poetry h6 .heti-meta { 723 | font-weight: 400; 724 | } 725 | @media screen and (min-width: 640px) { 726 | .heti--ancient h1 .heti-meta, 727 | .heti--ancient h2 .heti-meta, 728 | .heti--ancient h3 .heti-meta, 729 | .heti--ancient h4 .heti-meta, 730 | .heti--ancient h5 .heti-meta, 731 | .heti--ancient h6 .heti-meta, .heti--poetry h1 .heti-meta, 732 | .heti--poetry h2 .heti-meta, 733 | .heti--poetry h3 .heti-meta, 734 | .heti--poetry h4 .heti-meta, 735 | .heti--poetry h5 .heti-meta, 736 | .heti--poetry h6 .heti-meta { 737 | position: absolute; 738 | line-height: inherit; 739 | text-indent: 0; 740 | display: inline; 741 | margin-block-start: 4px; 742 | margin-inline-start: 8px; 743 | } 744 | } 745 | .heti--ancient .heti-meta, .heti--poetry .heti-meta { 746 | line-height: 24px; 747 | text-align: center; 748 | text-indent: 0; 749 | } 750 | .heti--ancient p { 751 | text-indent: 2em; 752 | } 753 | .heti--poetry p { 754 | text-align: center; 755 | text-indent: 0; 756 | } 757 | .heti--annotation p { 758 | margin-block-start: 0; 759 | margin-block-end: 0; 760 | line-height: 2.25; 761 | text-indent: 2em; 762 | } 763 | .heti--annotation em { 764 | -webkit-text-emphasis: filled circle; 765 | -webkit-text-emphasis-position: under; 766 | text-emphasis: filled circle; 767 | text-emphasis-position: under right; 768 | font-weight: 400; 769 | } 770 | .heti--annotation em:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)), .heti--annotation em:not(:lang(zh)) { 771 | -webkit-text-emphasis: none; 772 | text-emphasis: none; 773 | } 774 | .heti--annotation .heti-meta { 775 | margin-block-start: 12px; 776 | margin-block-end: 24px; 777 | } 778 | .heti .heti-meta { 779 | display: block; 780 | text-indent: 0; 781 | } 782 | .heti .heti-verse { 783 | text-align: center; 784 | text-indent: 0; 785 | } 786 | .heti .heti-large { 787 | font-size: 18px; 788 | line-height: 24px; 789 | } 790 | .heti .heti-x-large { 791 | font-size: 20px; 792 | line-height: 30px; 793 | letter-spacing: 0.05em; 794 | } 795 | .heti .heti-small { 796 | font-size: 14px; 797 | line-height: 24px; 798 | } 799 | .heti .heti-x-small { 800 | font-size: 12px; 801 | line-height: 18px; 802 | } 803 | .heti .heti-list-latin { 804 | list-style-type: upper-latin; 805 | } 806 | .heti .heti-list-latin ol { 807 | list-style-type: lower-roman; 808 | } 809 | .heti .heti-list-latin ol ol { 810 | list-style-type: lower-latin; 811 | } 812 | .heti .heti-list-han { 813 | list-style-type: cjk-ideographic; 814 | } 815 | .heti .heti-list-han ol { 816 | list-style-type: decimal; 817 | } 818 | .heti .heti-list-han ol ol { 819 | list-style-type: decimal-leading-zero; 820 | } 821 | .heti .heti-fn { 822 | margin-block-start: 59px; 823 | border-block-start: 1px solid; 824 | border-block-start-color: hsl(0deg, 0%, 80%); 825 | font-size: 14px; 826 | font-family: "Helvetica Neue", helvetica, arial, "Heti Hei", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 827 | line-height: 24px; 828 | } 829 | [data-darkmode=dark] .heti .heti-fn { 830 | border-block-start-color: hsl(0deg, 0%, 25%); 831 | } 832 | @media (prefers-color-scheme: dark) { 833 | [data-darkmode=auto] .heti .heti-fn { 834 | border-block-start-color: hsl(0deg, 0%, 25%); 835 | } 836 | } 837 | .heti .heti-fn ol { 838 | margin-block-start: 12px; 839 | margin-block-end: 0; 840 | } 841 | .heti .heti-fn li:target { 842 | background-color: hsl(210deg, 100%, 93%); 843 | } 844 | [data-darkmode=dark] .heti .heti-fn li:target { 845 | background-color: hsl(210deg, 40%, 38%); 846 | } 847 | @media (prefers-color-scheme: dark) { 848 | [data-darkmode=auto] .heti .heti-fn li:target { 849 | background-color: hsl(210deg, 40%, 38%); 850 | } 851 | } 852 | .heti .heti-hang { 853 | position: absolute; 854 | line-height: inherit; 855 | text-indent: 0; 856 | } 857 | .heti .heti-em { 858 | -webkit-text-emphasis: filled circle; 859 | -webkit-text-emphasis-position: under; 860 | text-emphasis: filled circle; 861 | text-emphasis-position: under right; 862 | } 863 | .heti .heti-em:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)), .heti .heti-em:not(:lang(zh)) { 864 | -webkit-text-emphasis: none; 865 | text-emphasis: none; 866 | } 867 | .heti .heti-ruby--inline { 868 | display: inline-flex; 869 | flex-direction: column-reverse; 870 | height: 1.5em; 871 | vertical-align: top; 872 | } 873 | .heti .heti-ruby--inline rt { 874 | display: inline; 875 | margin-bottom: -0.25em; 876 | line-height: 1; 877 | text-align: center; 878 | } 879 | .heti heti-spacing { 880 | display: inline; 881 | } 882 | .heti heti-spacing + sup, .heti heti-spacing + sub { 883 | margin-inline-start: 0; 884 | } 885 | .heti .heti-spacing-start { 886 | margin-inline-end: 0.25em; 887 | } 888 | .heti .heti-spacing-end { 889 | margin-inline-start: 0.25em; 890 | } 891 | .heti heti-adjacent { 892 | display: inline; 893 | text-spacing-trim: space-all; 894 | } 895 | .heti .heti-adjacent-half { 896 | margin-inline-end: -0.5em; 897 | } 898 | .heti .heti-adjacent-quarter { 899 | margin-inline-end: -0.25em; 900 | } 901 | -------------------------------------------------------------------------------- /_site/index.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * Project: Heti homepage 4 | * URL: https://github.com/sivan/heti 5 | * Author: Sivan [sun.sivan@gmail.com] 6 | */ 7 | /* normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ 8 | /* Document 9 | ========================================================================== */ 10 | /** 11 | * 1. Correct the line height in all browsers. 12 | * 2. Prevent adjustments of font size after orientation changes in iOS. 13 | */ 14 | html { 15 | line-height: 1.15; /* 1 */ 16 | -webkit-text-size-adjust: 100%; /* 2 */ 17 | } 18 | 19 | /* Sections 20 | ========================================================================== */ 21 | /** 22 | * Remove the margin in all browsers. 23 | */ 24 | body { 25 | margin: 0; 26 | } 27 | 28 | /** 29 | * Render the `main` element consistently in IE. 30 | */ 31 | main { 32 | display: block; 33 | } 34 | 35 | /** 36 | * Correct the font size and margin on `h1` elements within `section` and 37 | * `article` contexts in Chrome, Firefox, and Safari. 38 | */ 39 | h1 { 40 | font-size: 2em; 41 | margin: 0.67em 0; 42 | } 43 | 44 | /* Grouping content 45 | ========================================================================== */ 46 | /** 47 | * 1. Add the correct box sizing in Firefox. 48 | * 2. Show the overflow in Edge and IE. 49 | */ 50 | hr { 51 | box-sizing: content-box; /* 1 */ 52 | height: 0; /* 1 */ 53 | overflow: visible; /* 2 */ 54 | } 55 | 56 | /** 57 | * 1. Correct the inheritance and scaling of font size in all browsers. 58 | * 2. Correct the odd `em` font sizing in all browsers. 59 | */ 60 | pre { 61 | font-family: monospace, monospace; /* 1 */ 62 | font-size: 1em; /* 2 */ 63 | } 64 | 65 | /* Text-level semantics 66 | ========================================================================== */ 67 | /** 68 | * Remove the gray background on active links in IE 10. 69 | */ 70 | a { 71 | background-color: transparent; 72 | } 73 | 74 | /** 75 | * 1. Remove the bottom border in Chrome 57- 76 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 77 | */ 78 | abbr[title] { 79 | border-bottom: none; /* 1 */ 80 | text-decoration: underline; /* 2 */ 81 | text-decoration: underline dotted; /* 2 */ 82 | } 83 | 84 | /** 85 | * Add the correct font weight in Chrome, Edge, and Safari. 86 | */ 87 | b, 88 | strong { 89 | font-weight: bolder; 90 | } 91 | 92 | /** 93 | * 1. Correct the inheritance and scaling of font size in all browsers. 94 | * 2. Correct the odd `em` font sizing in all browsers. 95 | */ 96 | code, 97 | kbd, 98 | samp { 99 | font-family: monospace, monospace; /* 1 */ 100 | font-size: 1em; /* 2 */ 101 | } 102 | 103 | /** 104 | * Add the correct font size in all browsers. 105 | */ 106 | small { 107 | font-size: 80%; 108 | } 109 | 110 | /** 111 | * Prevent `sub` and `sup` elements from affecting the line height in 112 | * all browsers. 113 | */ 114 | sub, 115 | sup { 116 | font-size: 75%; 117 | line-height: 0; 118 | position: relative; 119 | vertical-align: baseline; 120 | } 121 | 122 | sub { 123 | bottom: -0.25em; 124 | } 125 | 126 | sup { 127 | top: -0.5em; 128 | } 129 | 130 | /* Embedded content 131 | ========================================================================== */ 132 | /** 133 | * Remove the border on images inside links in IE 10. 134 | */ 135 | img { 136 | border-style: none; 137 | } 138 | 139 | /* Forms 140 | ========================================================================== */ 141 | /** 142 | * 1. Change the font styles in all browsers. 143 | * 2. Remove the margin in Firefox and Safari. 144 | */ 145 | button, 146 | input, 147 | optgroup, 148 | select, 149 | textarea { 150 | font-family: inherit; /* 1 */ 151 | font-size: 100%; /* 1 */ 152 | line-height: 1.15; /* 1 */ 153 | margin: 0; /* 2 */ 154 | } 155 | 156 | /** 157 | * Show the overflow in IE. 158 | * 1. Show the overflow in Edge. 159 | */ 160 | button, 161 | input { /* 1 */ 162 | overflow: visible; 163 | } 164 | 165 | /** 166 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 167 | * 1. Remove the inheritance of text transform in Firefox. 168 | */ 169 | button, 170 | select { /* 1 */ 171 | text-transform: none; 172 | } 173 | 174 | /** 175 | * Correct the inability to style clickable types in iOS and Safari. 176 | */ 177 | button, 178 | [type=button], 179 | [type=reset], 180 | [type=submit] { 181 | -webkit-appearance: button; 182 | } 183 | 184 | /** 185 | * Remove the inner border and padding in Firefox. 186 | */ 187 | button::-moz-focus-inner, 188 | [type=button]::-moz-focus-inner, 189 | [type=reset]::-moz-focus-inner, 190 | [type=submit]::-moz-focus-inner { 191 | border-style: none; 192 | padding: 0; 193 | } 194 | 195 | /** 196 | * Restore the focus styles unset by the previous rule. 197 | */ 198 | button:-moz-focusring, 199 | [type=button]:-moz-focusring, 200 | [type=reset]:-moz-focusring, 201 | [type=submit]:-moz-focusring { 202 | outline: 1px dotted ButtonText; 203 | } 204 | 205 | /** 206 | * Correct the padding in Firefox. 207 | */ 208 | fieldset { 209 | padding: 0.35em 0.75em 0.625em; 210 | } 211 | 212 | /** 213 | * 1. Correct the text wrapping in Edge and IE. 214 | * 2. Correct the color inheritance from `fieldset` elements in IE. 215 | * 3. Remove the padding so developers are not caught out when they zero out 216 | * `fieldset` elements in all browsers. 217 | */ 218 | legend { 219 | box-sizing: border-box; /* 1 */ 220 | color: inherit; /* 2 */ 221 | display: table; /* 1 */ 222 | max-width: 100%; /* 1 */ 223 | padding: 0; /* 3 */ 224 | white-space: normal; /* 1 */ 225 | } 226 | 227 | /** 228 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 229 | */ 230 | progress { 231 | vertical-align: baseline; 232 | } 233 | 234 | /** 235 | * Remove the default vertical scrollbar in IE 10+. 236 | */ 237 | textarea { 238 | overflow: auto; 239 | } 240 | 241 | /** 242 | * 1. Add the correct box sizing in IE 10. 243 | * 2. Remove the padding in IE 10. 244 | */ 245 | [type=checkbox], 246 | [type=radio] { 247 | box-sizing: border-box; /* 1 */ 248 | padding: 0; /* 2 */ 249 | } 250 | 251 | /** 252 | * Correct the cursor style of increment and decrement buttons in Chrome. 253 | */ 254 | [type=number]::-webkit-inner-spin-button, 255 | [type=number]::-webkit-outer-spin-button { 256 | height: auto; 257 | } 258 | 259 | /** 260 | * 1. Correct the odd appearance in Chrome and Safari. 261 | * 2. Correct the outline style in Safari. 262 | */ 263 | [type=search] { 264 | -webkit-appearance: textfield; /* 1 */ 265 | outline-offset: -2px; /* 2 */ 266 | } 267 | 268 | /** 269 | * Remove the inner padding in Chrome and Safari on macOS. 270 | */ 271 | [type=search]::-webkit-search-decoration { 272 | -webkit-appearance: none; 273 | } 274 | 275 | /** 276 | * 1. Correct the inability to style clickable types in iOS and Safari. 277 | * 2. Change font properties to `inherit` in Safari. 278 | */ 279 | ::-webkit-file-upload-button { 280 | -webkit-appearance: button; /* 1 */ 281 | font: inherit; /* 2 */ 282 | } 283 | 284 | /* Interactive 285 | ========================================================================== */ 286 | /* 287 | * Add the correct display in Edge, IE 10+, and Firefox. 288 | */ 289 | details { 290 | display: block; 291 | } 292 | 293 | /* 294 | * Add the correct display in all browsers. 295 | */ 296 | summary { 297 | display: list-item; 298 | } 299 | 300 | /* Misc 301 | ========================================================================== */ 302 | /** 303 | * Add the correct display in IE 10+. 304 | */ 305 | template { 306 | display: none; 307 | } 308 | 309 | /** 310 | * Add the correct display in IE 10. 311 | */ 312 | [hidden] { 313 | display: none; 314 | } 315 | 316 | /* 简单模拟 css reset */ 317 | * { 318 | margin: 0; 319 | padding: 0; 320 | } 321 | 322 | ul, 323 | ol { 324 | list-style: none; 325 | } 326 | 327 | /* 模拟不知道哪里流传出来的垃圾代码 */ 328 | ul, 329 | li { 330 | list-style: none; 331 | } 332 | 333 | .container[data-bg-grid=grid-12] { 334 | background-size: 100% 12px; 335 | background-image: linear-gradient(transparent 11px, hsl(0deg, 0%, 93%) 1px), linear-gradient(to left, transparent 12px, hsl(0deg, 0%, 93%), transparent 13px), linear-gradient(to right, transparent 12px, hsl(0deg, 0%, 93%), transparent 13px); 336 | } 337 | .container[data-bg-grid=grid-12] .heti--vertical { 338 | outline: 1px solid hsl(0deg, 0%, 93%); 339 | background-size: 12px 100%; 340 | background-image: linear-gradient(to left, transparent 11px, hsl(0deg, 0%, 93%) 1px); 341 | } 342 | .container[data-bg-grid=grid-24] { 343 | background-size: 100% 24px; 344 | background-image: linear-gradient(transparent 23px, hsl(0deg, 0%, 93%) 1px), linear-gradient(to left, transparent 12px, hsl(0deg, 0%, 93%), transparent 13px), linear-gradient(to right, transparent 12px, hsl(0deg, 0%, 93%), transparent 13px); 345 | } 346 | .container[data-bg-grid=grid-24] .heti--vertical { 347 | outline: 1px solid hsl(0deg, 0%, 93%); 348 | background-size: 24px 100%; 349 | background-image: linear-gradient(to left, transparent 23px, hsl(0deg, 0%, 93%) 1px); 350 | } 351 | [data-darkmode=dark] .container[data-bg-grid=grid-12] { 352 | background-image: linear-gradient(transparent 11px, hsl(0deg, 0%, 20%) 1px), linear-gradient(to left, transparent 12px, hsl(0deg, 0%, 20%), transparent 13px), linear-gradient(to right, transparent 12px, hsl(0deg, 0%, 20%), transparent 13px); 353 | } 354 | [data-darkmode=dark] .container[data-bg-grid=grid-12] .heti--vertical { 355 | background-size: 12px 100%; 356 | background-image: linear-gradient(to left, transparent 11px, hsl(0deg, 0%, 20%) 1px); 357 | } 358 | [data-darkmode=dark] .container[data-bg-grid=grid-24] { 359 | background-image: linear-gradient(transparent 23px, hsl(0deg, 0%, 20%) 1px), linear-gradient(to left, transparent 12px, hsl(0deg, 0%, 20%), transparent 13px), linear-gradient(to right, transparent 12px, hsl(0deg, 0%, 20%), transparent 13px); 360 | } 361 | [data-darkmode=dark] .container[data-bg-grid=grid-24] .heti--vertical { 362 | background-size: 24px 100%; 363 | background-image: linear-gradient(to left, transparent 23px, hsl(0deg, 0%, 20%) 1px); 364 | } 365 | @media (prefers-color-scheme: dark) { 366 | [data-darkmode=auto] .container[data-bg-grid=grid-12] { 367 | background-image: linear-gradient(transparent 11px, hsl(0deg, 0%, 20%) 1px), linear-gradient(to left, transparent 12px, hsl(0deg, 0%, 20%), transparent 13px), linear-gradient(to right, transparent 12px, hsl(0deg, 0%, 20%), transparent 13px); 368 | } 369 | [data-darkmode=auto] .container[data-bg-grid=grid-12] .heti--vertical { 370 | background-size: 12px 100%; 371 | background-image: linear-gradient(to left, transparent 11px, hsl(0deg, 0%, 20%) 1px); 372 | } 373 | [data-darkmode=auto] .container[data-bg-grid=grid-24] { 374 | background-image: linear-gradient(transparent 23px, hsl(0deg, 0%, 20%) 1px), linear-gradient(to left, transparent 12px, hsl(0deg, 0%, 20%), transparent 13px), linear-gradient(to right, transparent 12px, hsl(0deg, 0%, 20%), transparent 13px); 375 | } 376 | [data-darkmode=auto] .container[data-bg-grid=grid-24] .heti--vertical { 377 | background-size: 24px 100%; 378 | background-image: linear-gradient(to left, transparent 23px, hsl(0deg, 0%, 20%) 1px); 379 | } 380 | } 381 | @media screen and (min-width: 640px) { 382 | .container[data-bg-grid=grid-12] { 383 | background-image: linear-gradient(transparent 11px, hsl(0deg, 0%, 93%) 1px), linear-gradient(to left, transparent 48px, hsl(0deg, 0%, 93%), transparent 49px), linear-gradient(to right, transparent 48px, hsla(0deg, 100%, 50%, 0.4), transparent 49px); 384 | } 385 | .container[data-bg-grid=grid-24] { 386 | background-image: linear-gradient(transparent 23px, hsl(0deg, 0%, 93%) 1px), linear-gradient(to left, transparent 48px, hsl(0deg, 0%, 93%), transparent 49px), linear-gradient(to right, transparent 48px, hsla(0deg, 100%, 50%, 0.4), transparent 49px); 387 | } 388 | [data-darkmode=dark] .container[data-bg-grid=grid-12] { 389 | background-image: linear-gradient(transparent 11px, hsl(0deg, 0%, 20%) 1px), linear-gradient(to left, transparent 48px, hsl(0deg, 0%, 20%), transparent 49px), linear-gradient(to right, transparent 48px, hsla(0deg, 100%, 65%, 0.4), transparent 49px); 390 | } 391 | [data-darkmode=dark] .container[data-bg-grid=grid-24] { 392 | background-image: linear-gradient(transparent 23px, hsl(0deg, 0%, 20%) 1px), linear-gradient(to left, transparent 48px, hsl(0deg, 0%, 20%), transparent 49px), linear-gradient(to right, transparent 48px, hsla(0deg, 100%, 65%, 0.4), transparent 49px); 393 | } 394 | } 395 | @media screen and (min-width: 640px) and (prefers-color-scheme: dark) { 396 | [data-darkmode=auto] .container[data-bg-grid=grid-12] { 397 | background-image: linear-gradient(transparent 11px, hsl(0deg, 0%, 20%) 1px), linear-gradient(to left, transparent 48px, hsl(0deg, 0%, 20%), transparent 49px), linear-gradient(to right, transparent 48px, hsla(0deg, 100%, 65%, 0.4), transparent 49px); 398 | } 399 | [data-darkmode=auto] .container[data-bg-grid=grid-24] { 400 | background-image: linear-gradient(transparent 23px, hsl(0deg, 0%, 20%) 1px), linear-gradient(to left, transparent 48px, hsl(0deg, 0%, 20%), transparent 49px), linear-gradient(to right, transparent 48px, hsla(0deg, 100%, 65%, 0.4), transparent 49px); 401 | } 402 | } 403 | 404 | .panel-list { 405 | display: inline-flex; 406 | margin: 0; 407 | padding: 0; 408 | list-style-type: none; 409 | --bg-color: hsl(240, 100%, 100%); 410 | --bg-tap-color: hsl(300, 3%, 94%); 411 | --border-color: hsla(0, 0%, 76%, 0.88); 412 | --border-inline-start-color: hsl(0, 0%, 76%); 413 | --border-inline-end-color: hsl(0, 0%, 76%); 414 | --border-block-start-color: hsl(0, 0%, 79%); 415 | --border-block-end-color: hsl(0, 0%, 65%); 416 | --text-color: hsl(60, 1%, 16%); 417 | --box-shadow-color: hsla(0, 0%, 86%, 0.54); 418 | --bg-highlight-color-start: hsl(216, 87%, 70%); 419 | --bg-highlight-color-end: hsl(215, 93%, 52%); 420 | --bg-highlight-tap-color-start: hsl(216, 92%, 65%); 421 | --bg-highlight-tap-color-end: hsl(215, 95%, 44%); 422 | --border-highlight-color: hsla(216, 90%, 57%, 0.88); 423 | --border-highlight-tap-color: hsla(216, 85%, 52%, 0.88); 424 | --border-inline-start-highlight-color: hsl(216, 90%, 57%); 425 | --border-inline-end-highlight-color: hsl(216, 90%, 57%); 426 | --border-inline-start-highlight-tap-color: hsl(216, 85%, 52%); 427 | --border-inline-end-highlight-tap-color: hsl(216, 85%, 52%); 428 | --border-block-start-highlight-color: hsl(216, 87%, 63%); 429 | --border-block-end-highlight-color: hsl(215, 99%, 49%); 430 | --border-block-start-highlight-tap-color: hsl(216, 93%, 57%); 431 | --border-block-end-highlight-tap-color: hsl(216, 100%, 42%); 432 | --text-highlight-color: hsl(0, 0%, 100%); 433 | } 434 | .panel-list--gray { 435 | --bg-color: hsl(180, 100%, 100%); 436 | --bg-tap-color: hsl(0, 0%, 94%); 437 | --border-color: hsla(0, 0%, 72%, 0.88); 438 | --border-inline-start-color: hsl(0, 0%, 76%); 439 | --border-inline-end-color: hsl(0, 0%, 76%); 440 | --border-block-start-color: hsl(0, 0%, 79%); 441 | --border-block-end-color: hsl(0, 0%, 65%); 442 | --text-color: hsl(0, 0%, 16%); 443 | --box-shadow-color: hsla(0, 0%, 86%, 0.54); 444 | --bg-highlight-color-start: hsl(225, 3%, 70%); 445 | --bg-highlight-color-end: hsl(228, 2%, 58%); 446 | --bg-highlight-tap-color-start: hsl(228, 2%, 58%); 447 | --bg-highlight-tap-color-end: hsl(240, 3%, 35%); 448 | --border-highlight-color: hsla(225, 2%, 64%, 0.88); 449 | --border-highlight-tap-color: hsla(228, 2%, 47%, 0.88); 450 | --border-inline-start-highlight-color: hsl(240, 3%, 59%); 451 | --border-inline-end-highlight-color: hsl(228, 2%, 54%); 452 | --border-inline-start-highlight-tap-color: hsl(240, 3%, 39%); 453 | --border-inline-end-highlight-tap-color: hsl(228, 3%, 39%); 454 | --border-block-start-highlight-color: hsl(240, 3%, 66%); 455 | --border-block-end-highlight-color: hsl(228, 2%, 50%); 456 | --border-block-start-highlight-tap-color: hsl(228, 2%, 51%); 457 | --border-block-end-highlight-tap-color: hsl(228, 4%, 26%); 458 | --text-highlight-color: hsl(0, 0%, 92%); 459 | } 460 | [data-darkmode=dark] .panel-list { 461 | --bg-color: hsl(225, 2%, 40%); 462 | --bg-tap-color: hsl(210, 2%, 49%); 463 | --border-color: hsla(210, 2%, 33%, 0.88); 464 | --border-inline-start-color: hsl(210, 2%, 33%); 465 | --border-inline-end-color: hsl(210, 2%, 33%); 466 | --border-block-start-color: hsl(225, 2%, 45%); 467 | --border-block-end-color: hsl(210, 2%, 40%); 468 | --text-color: hsl(0, 0%, 91%); 469 | --box-shadow-color: hsla(225, 4%, 21%, 0.54); 470 | --bg-highlight-color-start: hsl(216, 77%, 49%); 471 | --bg-highlight-color-end: hsl(216, 76%, 45%); 472 | --bg-highlight-tap-color-start: hsl(214, 83%, 55%); 473 | --bg-highlight-tap-color-end: hsl(215, 74%, 51%); 474 | --border-highlight-color: hsla(215, 77%, 47%, 0.88); 475 | --border-highlight-tap-color: hsla(215, 79%, 54%, 0.88); 476 | --border-inline-start-highlight-color: hsl(215, 77%, 47%); 477 | --border-inline-end-highlight-color: hsl(215, 77%, 47%); 478 | --border-inline-start-highlight-tap-color: hsl(215, 79%, 54%); 479 | --border-inline-end-highlight-tap-color: hsl(215, 79%, 54%); 480 | --border-block-start-highlight-color: hsl(216, 76%, 55%); 481 | --border-block-end-highlight-color: hsl(216, 76%, 44%); 482 | --border-block-start-highlight-tap-color: hsl(215, 84%, 60%); 483 | --border-block-end-highlight-tap-color: hsl(215, 74%, 51%); 484 | --text-highlight-color: hsl(0, 0%, 100%); 485 | } 486 | [data-darkmode=dark] .panel-list--gray { 487 | --bg-color: hsl(0, 0%, 40%); 488 | --bg-tap-color: hsl(0, 0%, 49%); 489 | --border-color: hsla(120, 1%, 34%, 0.88); 490 | --border-inline-start-color: hsl(0, 0%, 40%); 491 | --border-inline-end-color: hsl(360, 0%, 40%); 492 | --border-block-start-color: hsl(0, 0%, 46%); 493 | --border-block-end-color: hsl(120, 0%, 40%); 494 | --text-color: hsl(0, 0%, 91%); 495 | --box-shadow-color: hsla(0, 0%, 17%, 0.54); 496 | --bg-highlight-color-start: hsl(0, 0%, 60%); 497 | --bg-highlight-color-end: hsl(0, 0%, 60%); 498 | --bg-highlight-tap-color-start: hsl(0, 0%, 69%); 499 | --bg-highlight-tap-color-end: hsl(0, 0%, 69%); 500 | --border-highlight-color: hsla(0, 0%, 60%, 0.88); 501 | --border-highlight-tap-color: hsla(0, 0%, 69%, 0.88); 502 | --border-inline-start-highlight-color: hsl(0, 0%, 60%); 503 | --border-inline-end-highlight-color: hsl(0, 0%, 60%); 504 | --border-inline-start-highlight-tap-color: hsl(0, 0%, 69%); 505 | --border-inline-end-highlight-tap-color: hsl(0, 0%, 69%); 506 | --border-block-start-highlight-color: hsl(0, 0%, 64%); 507 | --border-block-end-highlight-color: hsl(0, 0%, 60%); 508 | --border-block-start-highlight-tap-color: hsl(0, 0%, 73%); 509 | --border-block-end-highlight-tap-color: hsl(0, 0%, 69%); 510 | --text-highlight-color: hsl(0, 0%, 15%); 511 | } 512 | @media (prefers-color-scheme: dark) { 513 | [data-darkmode=auto] .panel-list { 514 | --bg-color: hsl(225, 2%, 40%); 515 | --bg-tap-color: hsl(210, 2%, 49%); 516 | --border-color: hsla(210, 2%, 33%, 0.88); 517 | --border-inline-start-color: hsl(210, 2%, 33%); 518 | --border-inline-end-color: hsl(210, 2%, 33%); 519 | --border-block-start-color: hsl(225, 2%, 45%); 520 | --border-block-end-color: hsl(210, 2%, 40%); 521 | --text-color: hsl(0, 0%, 91%); 522 | --box-shadow-color: hsla(225, 4%, 21%, 0.54); 523 | --bg-highlight-color-start: hsl(216, 77%, 49%); 524 | --bg-highlight-color-end: hsl(216, 76%, 45%); 525 | --bg-highlight-tap-color-start: hsl(214, 83%, 55%); 526 | --bg-highlight-tap-color-end: hsl(215, 74%, 51%); 527 | --border-highlight-color: hsla(215, 77%, 47%, 0.88); 528 | --border-highlight-tap-color: hsla(215, 79%, 54%, 0.88); 529 | --border-inline-start-highlight-color: hsl(215, 77%, 47%); 530 | --border-inline-end-highlight-color: hsl(215, 77%, 47%); 531 | --border-inline-start-highlight-tap-color: hsl(215, 79%, 54%); 532 | --border-inline-end-highlight-tap-color: hsl(215, 79%, 54%); 533 | --border-block-start-highlight-color: hsl(216, 76%, 55%); 534 | --border-block-end-highlight-color: hsl(216, 76%, 44%); 535 | --border-block-start-highlight-tap-color: hsl(215, 84%, 60%); 536 | --border-block-end-highlight-tap-color: hsl(215, 74%, 51%); 537 | --text-highlight-color: hsl(0, 0%, 100%); 538 | } 539 | [data-darkmode=auto] .panel-list--gray { 540 | --bg-color: hsl(0, 0%, 40%); 541 | --bg-tap-color: hsl(0, 0%, 49%); 542 | --border-color: hsla(120, 1%, 34%, 0.88); 543 | --border-inline-start-color: hsl(0, 0%, 40%); 544 | --border-inline-end-color: hsl(360, 0%, 40%); 545 | --border-block-start-color: hsl(0, 0%, 46%); 546 | --border-block-end-color: hsl(120, 0%, 40%); 547 | --text-color: hsl(0, 0%, 91%); 548 | --box-shadow-color: hsla(0, 0%, 17%, 0.54); 549 | --bg-highlight-color-start: hsl(0, 0%, 60%); 550 | --bg-highlight-color-end: hsl(0, 0%, 60%); 551 | --bg-highlight-tap-color-start: hsl(0, 0%, 69%); 552 | --bg-highlight-tap-color-end: hsl(0, 0%, 69%); 553 | --border-highlight-color: hsla(0, 0%, 60%, 0.88); 554 | --border-highlight-tap-color: hsla(0, 0%, 69%, 0.88); 555 | --border-inline-start-highlight-color: hsl(0, 0%, 60%); 556 | --border-inline-end-highlight-color: hsl(0, 0%, 60%); 557 | --border-inline-start-highlight-tap-color: hsl(0, 0%, 69%); 558 | --border-inline-end-highlight-tap-color: hsl(0, 0%, 69%); 559 | --border-block-start-highlight-color: hsl(0, 0%, 64%); 560 | --border-block-end-highlight-color: hsl(0, 0%, 60%); 561 | --border-block-start-highlight-tap-color: hsl(0, 0%, 73%); 562 | --border-block-end-highlight-tap-color: hsl(0, 0%, 69%); 563 | --text-highlight-color: hsl(0, 0%, 15%); 564 | } 565 | } 566 | .panel-list + .panel-list { 567 | margin-inline-start: 12px; 568 | } 569 | .panel-list label { 570 | position: relative; 571 | display: block; 572 | box-sizing: border-box; 573 | height: 20px; 574 | padding: 0; 575 | padding-inline-start: 12px; 576 | padding-inline-end: 12px; 577 | border: 1px solid; 578 | font-size: 12px; 579 | line-height: 18px; 580 | text-align: center; 581 | user-select: none; 582 | background-color: var(--bg-color); 583 | border-color: var(--border-color); 584 | border-block-start-color: var(--border-block-start-color); 585 | border-block-end-color: var(--border-block-end-color); 586 | color: var(--text-color); 587 | box-shadow: 0 1px 0 var(--box-shadow-color); 588 | } 589 | .panel-list label:active { 590 | background-color: var(--bg-tap-color); 591 | } 592 | .panel-list li { 593 | margin-inline-end: -1px; 594 | } 595 | .panel-list li:first-child label { 596 | border-top-left-radius: 4px; 597 | border-bottom-left-radius: 4px; 598 | border-inline-start-color: var(--border-inline-start-color); 599 | } 600 | .panel-list li:last-child label { 601 | border-top-right-radius: 4px; 602 | border-bottom-right-radius: 4px; 603 | border-inline-end-color: var(--border-inline-end-color); 604 | } 605 | .panel-list input { 606 | display: none; 607 | } 608 | .panel-list input:checked + label { 609 | z-index: 1; 610 | background-image: linear-gradient(to bottom, var(--bg-highlight-color-start), var(--bg-highlight-color-end)); 611 | border-color: var(--border-highlight-color); 612 | border-block-start-color: var(--border-block-start-highlight-color); 613 | border-block-end-color: var(--border-block-end-highlight-color); 614 | color: var(--text-highlight-color); 615 | } 616 | .panel-list input:checked + label:active { 617 | background-image: linear-gradient(to bottom, var(--bg-highlight-tap-color-start), var(--bg-highlight-tap-color-end)); 618 | border-color: var(--border-highlight-tap-color); 619 | border-block-start-color: var(--border-block-start-highlight-tap-color); 620 | border-block-end-color: var(--border-block-end-highlight-tap-color); 621 | } 622 | .panel-list--icon label { 623 | width: 30px; 624 | padding-inline-start: 8px; 625 | padding-inline-end: 8px; 626 | } 627 | 628 | .anchor { 629 | margin-inline-start: 0.25em; 630 | } 631 | 632 | @media screen and (min-width: 640px) { 633 | .article .anchor { 634 | position: absolute; 635 | left: -1em; 636 | width: 1em; 637 | margin-inline-start: 0; 638 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 639 | font-weight: 400; 640 | line-height: inherit; 641 | text-align: center; 642 | opacity: 0; 643 | transition: opacity 0.2s linear; 644 | } 645 | .article .anchor:hover { 646 | text-decoration: none; 647 | border: 0; 648 | } 649 | .article h1, 650 | .article h2, 651 | .article h3, 652 | .article h4, 653 | .article h5, 654 | .article h6 { 655 | position: relative; 656 | } 657 | .article h1:hover .anchor, 658 | .article h2:hover .anchor, 659 | .article h3:hover .anchor, 660 | .article h4:hover .anchor, 661 | .article h5:hover .anchor, 662 | .article h6:hover .anchor { 663 | opacity: 1; 664 | } 665 | } 666 | .card { 667 | position: relative; 668 | left: -12px; 669 | width: 100%; 670 | margin-block-start: 24px; 671 | margin-block-end: 48px; 672 | padding-block-start: 12px; 673 | padding-block-end: 12px; 674 | padding-inline-start: 12px; 675 | padding-inline-end: 12px; 676 | border-radius: 2px; 677 | box-shadow: 0 4px 16px hsla(0deg, 0%, 0%, 0.16); 678 | background-color: hsl(0deg, 0%, 100%); 679 | } 680 | .article .card { 681 | text-align: left; 682 | } 683 | .card > figcaption { 684 | display: inline-block; 685 | margin-block-start: 16px; 686 | padding-block-start: 4px; 687 | padding-block-end: 3px; 688 | padding-inline-start: 0; 689 | padding-inline-end: 72px; 690 | line-height: 24px; 691 | border-block-start: 1px solid hsl(0deg, 0%, 93%); 692 | } 693 | 694 | .card__vertical-container { 695 | box-sizing: border-box; 696 | width: 100%; 697 | height: 30em; 698 | border: 1px solid hsl(0deg, 0%, 93%); 699 | overflow: auto; 700 | writing-mode: vertical-rl; 701 | } 702 | 703 | @media screen and (min-width: 640px) { 704 | .card { 705 | box-sizing: border-box; 706 | left: -20%; 707 | width: 140%; 708 | padding-block-start: 24px; 709 | padding-block-end: 24px; 710 | padding-inline-start: 32px; 711 | padding-inline-end: 32px; 712 | } 713 | } 714 | [data-darkmode=dark] .card { 715 | background-color: hsl(0deg, 0%, 16%); 716 | } 717 | [data-darkmode=dark] .card > figcaption { 718 | border-block-start: 1px solid hsl(0deg, 0%, 20%); 719 | } 720 | [data-darkmode=dark] .card .card__vertical-container { 721 | border-color: hsl(0deg, 0%, 20%); 722 | } 723 | @media (prefers-color-scheme: dark) { 724 | [data-darkmode=auto] .card { 725 | background-color: hsl(0deg, 0%, 16%); 726 | } 727 | [data-darkmode=auto] .card > figcaption { 728 | border-block-start: 1px solid hsl(0deg, 0%, 20%); 729 | } 730 | [data-darkmode=auto] .card .card__vertical-container { 731 | border-color: hsl(0deg, 0%, 20%); 732 | } 733 | } 734 | 735 | /** 基础样式 **/ 736 | body { 737 | font-family: -apple-system, system-ui, "Helvetica Neue", Helvetica, Arial, "Heti Hei", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 738 | background-color: hsl(0deg, 0%, 100%); 739 | } 740 | 741 | a { 742 | color: hsl(217deg, 89%, 61%); 743 | text-decoration: none; 744 | } 745 | 746 | .container { 747 | box-sizing: border-box; 748 | padding-block-start: 12px; 749 | padding-block-end: 72px; 750 | padding-inline-start: 12px; 751 | padding-inline-end: 12px; 752 | } 753 | 754 | /** 首页样式 **/ 755 | .article { 756 | margin-inline-start: auto; 757 | margin-inline-end: auto; 758 | } 759 | .article__nav ol { 760 | margin-block-start: 24px; 761 | margin-block-end: 24px; 762 | } 763 | 764 | /** 控制栏样式 **/ 765 | .panel { 766 | position: fixed; 767 | z-index: 2; 768 | right: 14px; 769 | top: 14px; 770 | display: flex; 771 | text-align: right; 772 | } 773 | 774 | /** 演示区块 **/ 775 | .demo { 776 | margin-block-start: 24px; 777 | margin-block-end: 24px; 778 | } 779 | 780 | .section { 781 | width: 100%; 782 | max-height: 85vh; 783 | margin-block-start: 12px; 784 | margin-block-end: 12px; 785 | overflow: auto; 786 | } 787 | 788 | @media screen and (min-width: 640px) { 789 | body { 790 | min-width: 640px; 791 | background-color: hsl(0deg, 0%, 93%); 792 | } 793 | .container { 794 | box-sizing: border-box; 795 | width: 80%; 796 | min-width: 640px; 797 | max-width: 768px; 798 | margin-block-start: 48px; 799 | margin-block-end: 72px; 800 | margin-inline-start: auto; 801 | margin-inline-end: auto; 802 | padding-block-start: 48px; 803 | padding-block-end: 48px; 804 | padding-inline-start: 48px; 805 | padding-inline-end: 48px; 806 | border-radius: 2px; 807 | box-shadow: 0 8px 32px hsla(0deg, 0%, 0%, 0.32); 808 | background-color: hsl(0deg, 0%, 100%); 809 | } 810 | .section { 811 | max-height: none; 812 | overflow: visible; 813 | } 814 | } 815 | @media screen and (min-width: 900px) { 816 | .article__nav { 817 | position: relative; 818 | z-index: 1; 819 | float: right; 820 | width: 192px; 821 | margin-block-start: -1px; 822 | margin-block-end: 12px; 823 | margin-inline-start: 32px; 824 | margin-inline-end: -16px; 825 | padding-block-start: 12px; 826 | padding-block-end: 12px; 827 | padding-inline-start: 8px; 828 | padding-inline-end: 8px; 829 | border: 1px solid hsl(0deg, 0%, 93%); 830 | border-radius: 2px; 831 | } 832 | .article__nav ol { 833 | margin-block-start: 12px; 834 | margin-block-end: 0; 835 | } 836 | .article__nav ol ol { 837 | margin-block-start: 0; 838 | } 839 | } 840 | [data-darkmode=dark] body { 841 | background-color: hsl(0deg, 0%, 24%); 842 | } 843 | [data-darkmode=dark] a { 844 | color: hsl(217deg, 49%, 61%); 845 | } 846 | [data-darkmode=dark] a:visited { 847 | color: hsl(217deg, 49%, 36%); 848 | } 849 | [data-darkmode=dark] .container { 850 | background-color: hsl(0deg, 0%, 16%); 851 | color: hsl(0deg, 0%, 64%); 852 | } 853 | [data-darkmode=dark] .article__nav { 854 | border-color: hsl(0deg, 0%, 20%); 855 | } 856 | 857 | @media (prefers-color-scheme: dark) { 858 | [data-darkmode=auto] body { 859 | background-color: hsl(0deg, 0%, 24%); 860 | } 861 | [data-darkmode=auto] a { 862 | color: hsl(217deg, 49%, 61%); 863 | } 864 | [data-darkmode=auto] a:visited { 865 | color: hsl(217deg, 49%, 36%); 866 | } 867 | [data-darkmode=auto] .container { 868 | background-color: hsl(0deg, 0%, 16%); 869 | color: hsl(0deg, 0%, 64%); 870 | } 871 | [data-darkmode=auto] .article__nav { 872 | border-color: hsl(0deg, 0%, 20%); 873 | } 874 | } 875 | -------------------------------------------------------------------------------- /_site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 赫蹏 - 一个简约又简单的网页中文排版增强 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |

赫蹏

15 |
古代称用以书写的小幅绢帛。后亦以借指纸。《汉书·外戚传下·孝成赵皇后》:「武(籍武)发篋中,有裹药二枚,赫蹏书。」颜师古注:「邓展曰:『赫音兄弟鬩墙之鬩。』应劭曰:『赫蹏,薄小纸也。』」赵彦卫 《云麓漫钞》卷七:「《赵后传》所谓『赫蹏』者,注云『薄小纸』,然其寔亦縑帛。」
16 | 17 | 46 | 47 |

介绍#

48 |

()()是专为中文网页内容设计的排版样式增强。它基于通行的中文排版规范,可为网站的读者带来更好的内容阅读体验。它的主要特性有:

49 | 63 |

总之,用上就会变好看。

64 | 65 |
66 | 67 |

使用方法#

68 |

项目地址:https://github.com/sivan/heti,使用方法如下:

69 |
    70 |
  1. 71 | 在页面的</head>标签前中引入heti.css样式文件: 72 |
    <link rel="stylesheet" href="//unpkg.com/heti/umd/heti.min.css">
    73 |
  2. 74 |
  3. 75 | 在要作用的容器元素上增加class="heti"的类名即可: 76 | 77 |
    <article class="entry heti">
     78 |   <h1>我的世界观</h1>
     79 |   <p>有钱人的生活就是这么朴实无华,且枯燥。</p>
     80 |   ……
     81 | </article>
    82 |
  4. 83 |
84 | 注:赫蹏是正文区域的样式增强,不是normalize.cssCSS Reset的替代。因此不建议将它作用在根标签(如<body><div class="container">)上。 85 | 86 |
87 | 88 |

效果示例#

89 |

本页面全页应用了赫蹏样式,所见即所得。下面是内置的多种排版效果演示。

90 | 91 |

古文#

92 |
93 | 如何使用? 94 |

为容器元素<div class="heti">添加名为heti--ancient的class即可实现古文版式:

95 |
<div class="heti heti--ancient">...</div>
96 |
97 |
98 | 示例 99 |
100 |
101 |

出师表

102 |

作者:諸葛亮(181年~234年10月8日)

103 |

先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。

104 |

宫中府中,俱为一体;陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理;不宜偏私,使内外异法也。

105 |

侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下:愚以为宫中之事,事无大小,悉以咨之,然后施行,必能裨补阙漏,有所广益。

106 |

将军向宠,性行淑均,晓畅军事,试用于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和睦,优劣得所。

107 |

亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓、灵也。侍中、尚书、长史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。

108 |

臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间,尔来二十有一年矣。

109 |

先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐托付不效,以伤先帝之明;故五月渡泸,深入不毛。今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。

110 |

愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎;陛下亦宜自谋,以咨诹善道,察纳雅言,深追先帝遗诏。臣不胜受恩感激。

111 |

今当远离,临表涕零,不知所言。

112 |
113 |
114 |
115 | 116 |

诗词#

117 |
118 | 如何使用? 119 |
    120 |
  • 121 | 诗词:为容器元素<div class="heti">添加名为heti--poetry的class实现诗词版式: 122 |
    <div class="heti heti--poetry">
    123 |   <h2>九月九日忆山东兄弟<span class="heti-meta heti-small">[唐]<abbr title="号摩诘居士">王维</abbr></span></h2>
    124 |   <p class="heti-x-large">
    125 |     独在异乡为异客<span class="heti-hang">,</span><br>
    126 |     每逢佳节倍思亲<span class="heti-hang">。</span><br>
    127 |     遥知兄弟登高处<span class="heti-hang">,</span><br>
    128 |     遍插茱萸少一人<span class="heti-hang">。</span>
    129 |   </p>
    130 | </div>
    131 |
  • 132 |
  • 133 | 诗节:在古文版式<div class="heti heti--ancient">中,为诗句添加名为heti-verse的class可以将其居中显示: 134 |
    <div class="heti heti--ancient">
    135 |   <h2>一剪梅·红藕香残玉簟秋<span class="heti-meta heti-small">[宋]<abbr title="号易安居士">李清照</abbr></span></h2>
    136 |   <p class="heti-verse heti-x-large">
    137 |     红藕香残玉簟秋。轻解罗裳,独上兰舟<span class="heti-hang">。</span><br>
    138 |     云中谁寄锦书来,雁字回时,月满西楼<span class="heti-hang">。</span><br>
    139 |     花自飘零水自流。一种相思,两处闲愁<span class="heti-hang">。</span><br>
    140 |     此情无计可消除,才下眉头,却上心头<span class="heti-hang">。</span>
    141 |   </p>
    142 | </div>
    143 |
  • 144 |
  • 搭配使用标点悬挂<span class="heti-hang">、元信息<span class="heti-meta heti-small">来丰富展示效果。
  • 145 |
146 |
147 |
148 | 示例 149 |
150 |
151 |

一剪梅·红藕香残玉簟秋[宋]李清照

152 |

153 | 红藕香残玉簟秋。轻解罗裳,独上兰舟
154 | 云中谁寄锦书来,雁字回时,月满西楼
155 | 花自飘零水自流。一种相思,两处闲愁
156 | 此情无计可消除,才下眉头,却上心头 157 |

158 |
159 |
160 |
161 |

赠汪伦[唐]李白

162 |

163 | 李白乘舟将欲行
164 | 忽闻岸上踏歌声
165 | 桃花潭水深千尺
166 | 不及汪伦送我情 167 |

168 |
169 |
170 |
171 | 172 |

行间注#

173 |
174 | 如何使用? 175 |

为容器元素<div class="heti">添加名为heti--annotation的class后,搭配<ruby>元素即可实现整齐的行间注效果:

176 |
<div class="heti heti--annotation">...</div>
177 |
178 |
179 | 示例 180 |
181 |
182 |

庖丁解牛

183 |

作者:庄周(公元前369~公元前286年)

184 |

吾生也有涯,而知也无涯。以有涯随无涯,殆已!已而为知者,殆而已矣!为善无近名,为恶无近刑。缘督以为经,可以保身,可以全生,可以养亲,可以尽年。

185 |

(páo)为文惠君解牛,手之所触,肩之所倚,足之所履,膝之所()(huā)(xiǎng)然,奏刀(huō),莫不中音。合于《桑林》之舞,乃中《经首》之会。

186 |

文惠君曰:「嘻,善哉!技()至此乎?」

187 |

庖丁释刀对曰:「臣之所好者,道也,进乎技矣。始臣之解牛之时,所见无非牛者。三年之后,未尝见全牛也。方今之时,臣以神遇而不以目视,官知止而神欲行。依乎天理批大()导大(kuǎn)固然,技经肯(qìng)之未尝,而况大()乎!良庖岁更刀,割也;族庖月更刀,折也。今臣之刀十九年矣,所解数千牛矣,而刀刃若新发于(xíng)。彼节者有间,而刀刃者无厚;以无厚入有间,恢恢乎其于游刃必有余地矣,是以十九年而刀刃若新发于硎。虽然,每至于族,吾见其难为,(chù)然为戒,视为止,行为迟。动刀甚微,(huò)然已解,如土委地。提刀而立,为之四顾,为之(chóu)(chú)满志,善刀而藏之。」

188 |

文惠君曰:「善哉!吾闻庖丁之言,得养生焉。」

189 |
190 |
191 |
192 | 193 |

多栏排版#

194 |

赫蹏预置了多种多栏布局类,可以按栏数或每栏行宽进行设置。

195 |
196 | 如何使用? 197 |

为容器元素<div class="heti">添加名为heti--columns-2的class即可实现双栏排版:

198 |
<div class="heti heti--columns-2">...</div>
199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 |
方式对应类名可选数值
按栏目数量heti--columns-32, 3, 4
按每栏行宽heti--columns-16em16em, 20em, 24em, … +4em, … , 48em
220 |
221 |
222 | 示例 223 |
224 |
225 |

先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。

226 |

宫中府中,俱为一体;陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理;不宜偏私,使内外异法也。

227 |

侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下:愚以为宫中之事,事无大小,悉以咨之,然后施行,必能裨补阙漏,有所广益。

228 |

将军向宠,性行淑均,晓畅军事,试用于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和睦,优劣得所。

229 |

亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓、灵也。侍中、尚书、长史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。

230 |

臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间,尔来二十有一年矣。

231 |

先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐托付不效,以伤先帝之明;故五月渡泸,深入不毛。今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。

232 |

愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎;陛下亦宜自谋,以咨诹善道,察纳雅言,深追先帝遗诏。臣不胜受恩感激。

233 |

今当远离,临表涕零,不知所言。

234 |
235 |
多栏排版演示
236 |
237 |
238 | 239 |

竖排排版#

240 |

赫蹏预置了传统的竖排(直排)方向排版,同样贴合栅格。

241 |
242 | 如何使用? 243 |

为容器元素<div class="heti">添加名为heti--vertical的class即可实现竖排布局:

244 |
<div class="heti heti--vertical">...</div>
245 |
246 |
247 | 示例 248 |
249 |
250 |
251 |

出師表

252 |

作者:諸葛亮(181年-234年10月8日)

253 |

先帝創業未半,而中道崩殂;今天下三分,益州疲弊,此誠危急存亡之秋也﹗然侍衞之臣,不懈於內;忠志之士,忘身於外者,蓋追先帝之殊遇,欲報之於陛下也。

254 |

誠宜開張聖聽,以光先帝遺德,恢弘志士之氣﹔不宜妄自菲薄,引喻失義,以塞忠諫之路也。

255 |

宮中、府中,俱為一體;陟罰臧否,不宜異同。若有作姦、犯科,及為忠善者,宜付有司,論其刑賞,以昭陛下平明之治;不宜偏私,使內外異法也。

256 |

侍中、侍郎郭攸之、費禕、董允等,此皆良實,志慮忠純,是以先帝簡拔以遺陛下。愚以為宮中之事,事無大小,悉以咨之,然後施行,必能裨補闕漏,有所廣益。將軍向寵,性行淑均,曉暢軍事,試用於昔日,先帝稱之曰「能」,是以眾議舉寵為督。愚以為營中之事,悉以咨之,必能使行陣和睦,優劣得所。

257 |

親賢臣,遠小人,此先漢所以興隆也﹔親小人,遠賢臣,此後漢所以傾頹也。先帝在時,每與臣論此事,未嘗不歎息痛恨於桓、靈也!侍中、尚書、長史、參軍,此悉貞良死節之臣,願陛下親之、信之,則漢室之隆,可計日而待也。

258 |

臣本布衣,躬耕於南陽,苟全性命於亂世,不求聞達於諸侯。先帝不以臣卑鄙,猥自枉屈,三顧臣於草廬之中,諮臣以當世之事;由是感激,遂許先帝以驅馳。後值傾覆,受任於敗軍之際,奉命於危難之間,爾來二十有一年矣。先帝知臣謹慎,故臨崩寄臣以大事也。受命以來,夙夜憂歎,恐託付不效,以傷先帝之明。故五月渡瀘,深入不毛。今南方已定,兵甲已足,當獎率三軍,北定中原,庶竭駑鈍,攘除姦凶,興復漢室,還於舊都。此臣所以報先帝而忠陛下之職分也。至於斟酌損益,進盡忠言,則攸之、禕、允之任也。

259 |

願陛下託臣以討賊興復之效;不效,則治臣之罪,以告先帝之靈。若無興德之言,則責攸之、禕、允等之慢,以彰其咎。陛下亦宜自謀,以諮諏善道,察納雅言,深追先帝遺詔。臣不勝受恩感激。今當遠離,臨表涕零,不知所言!

260 |
261 |
262 |
竖排排版演示
263 |
264 |
265 | 266 |

英文排版#

267 |
268 | 效果演示 269 |
270 |
271 |

Lorem Ipsum

272 |

There is no one who loves pain itself, who seeks after it and wants to have it, simply because it is pain...

273 |

Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.

274 |

The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham.

275 |
276 |
277 |
278 | 279 |
280 | 281 |

设计原则#

282 |

赫蹏项目的初衷很简单:它不作为一个CSS Reset出现,而是根据通行的中文排版规范,对网页正文区域进行排版样式增强。在部分CSS特性尚未有浏览器支持前,可通过JavaScript实现功能补充。

283 |

文字

284 |

参考《中文排版需求[2]》中描述的常用书籍排版字体,赫蹏提供了黑体、宋体和传统三种字体风格,前两者分别对应无衬线、衬线字体族。文字默认采用16px作为标准字号。在标题等文字较大的情况下,会适当地增加字间距以便获得更好地可读性。

285 |
286 | 查看字体风格详细对照表 287 |
288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 310 | 311 | 312 | 313 | 314 | 319 | 320 | 321 | 322 | 323 | 328 | 329 | 330 | 331 | 332 | 337 | 338 | 339 | 340 | 341 | 346 | 347 | 348 | 349 | 350 | 358 | 359 | 360 | 361 | 362 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 |
各字体族下不同标签对应的字体
黑体宋体传统备注
标题黑体宋体楷体 306 |
307 |

记忆中的站台

308 |
309 |
正文宋体 315 |
316 |

那是一个风雨交加的夜晚。

317 |
318 |
引用楷体 324 |
325 |
锣鼓喧天,鞭炮齐鸣,红旗招展,人山人海。
326 |
327 |
强调宋体 333 |
334 |

父亲特意嘱咐了我两句。

335 |
336 |
对话楷体 342 |
343 |

他说:我买几个橘子去。你就在此地,不要走动。

344 |
345 |
图例黑体 351 |
352 |
353 | 354 |
橘子
355 |
356 |
357 |
表头黑体 363 |
364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 |
当时的情形
角色物品
父亲橘子
车票
379 |
380 |
角标黑体黑体黑体鲁迅[1]曾经没有说过
391 |
392 |
393 |

标点

394 |

参考《中文排版需求》制定符号样式。唯一的差异在于简体中文一律采用直角引号(「」)替代弯引号(“”),这样可以保持字符等宽。

395 |
396 | 查看如何将引号设置为弯引号(“”) 397 |

通过源码引用的方式覆盖`_variables.scss`文件中`$chinese-quote-set`变量的值为`cn`即可将引号设为GB/T 15834-2011的国家标准。

398 |
399 |

间距

400 |

为保持页面元素总是贴合垂直栅格,块级元素(段落、列表、表格等)采用一行行高作为底边距,半行行高作为顶边距。标题根据亲密性原则采用相反的边距设计。

401 | 402 |
403 | 404 |

附录#

405 |

兼容性#

406 |

赫蹏在间距、边框、位置属性上采用了Logical properties,在所有现代浏览器上表现良好。

407 |
408 | 查看兼容性列表 409 |
410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 |
兼容性列表(未经充分测试)
ChromeSafariFirefoxIEEdge
兼容性6912.13暂未支持79
429 |
430 |
431 | 432 |

标签示例表#

433 |
434 | 查看标签示例表 435 |
436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 |
常用标签样式示例表
类型标签效果
链接<a href="https://github.com/sivan/heti" title="赫蹏">heti.css</a>heti.css
缩写<abbr title="Cascading Style Sheets">CSS</abbr>CSS
代码<code>.heti { star: 5; }</code>.heti { star: 5; }
专名号此时来自<u title="位于山东省聊城市阳谷县城东">景阳冈</u>的<u>武松</u>大喝一声:<q>纳命来!</q>此时来自景阳冈武松大喝一声:纳命来!
文本变动这次考试,我考了<del datetime="17:00:00">58</del><ins datetime="18:15:00">98</ins>分呢!这次考试,我考了5898分呢!
文本更新因为谁也不认识,所以最后我们决定念<s>dí</s>tí。因为谁也不认识,所以最后我们决定念tí。
引号窃·格瓦拉曾经说过:<q>打工是不可能打工的。</q>窃·格瓦拉曾经说过:打工是不可能打工的。
术语<dfn>窃·格瓦拉</dfn>,中国大陆网络红人、罪犯。被奉为百度「戒赌吧」400万会员的「精神领袖」。窃·格瓦拉,中国大陆网络红人、罪犯。被奉为百度「戒赌吧」400万会员的「精神领袖」。
标记这道题<mark>必考</mark>,你们爱记不记。这道题必考,你们爱记不记。
强调稳住,<em>我们能赢</em>!稳住,我们能赢
着重号我们<span class="heti-em">必将</span>战胜这场疫情。我们必将战胜这场疫情。
499 |
500 |
501 | 502 |

增强脚本beta#

503 |

由于部分CSS特性尚未有浏览器支持等原因,可选择使用增强脚本进行排版效果优化。在页面的</body>标签前引入JavaScript脚本后初始化即可:

504 |
<script src="//unpkg.com/heti/umd/heti-addon.min.js"></script>
505 | <script>
506 |   const heti = new Heti('.heti');
507 |   heti.autoSpacing();
508 | </script>
509 |

目前支持的功能有:

510 | 514 |

效果演示:

515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 525 | 526 | 527 | 528 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 540 | 541 | 542 | 543 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 |
增强脚本示例表
中西文混排美化
默认文本 523 |
Hello, world!是大家第一次学习Programming时最常写的demo,它看似简单,但对有些人来说寥寥数语有时也会产生bug。
524 |
脚本效果 529 |
Hello, world!是大家第一次学习Programming时最常写的demo,它看似简单,但对有些人来说寥寥数语有时也会产生bug。
530 |
标点挤压
默认文本 538 |
古代称用以书写的小幅绢帛。后亦以借指纸。《汉书·外戚传下·孝成赵皇后》:「武(籍武 )发篋中,有裹药二枚,赫蹏书。」颜师古注:「邓展曰:『赫音兄弟鬩墙之鬩。』应劭曰:『赫蹏,薄小纸也。』」赵彦卫《云麓漫钞》卷七:「《赵后传》所谓『赫蹏』者,注云『薄小纸』,然其寔亦縑帛。」
539 |
脚本效果 544 |
古代称用以书写的小幅绢帛。后亦以借指纸。《汉书·外戚传下·孝成赵皇后》:「武(籍武 )发篋中,有裹药二枚,赫蹏书。」颜师古注:「邓展曰:『赫音兄弟鬩墙之鬩。』应劭曰:『赫蹏,薄小纸也。』」赵彦卫《云麓漫钞》卷七:「《赵后传》所谓『赫蹏』者,注云『薄小纸』,然其寔亦縑帛。」
545 |
560 | 561 |

开源协议#

562 |

「赫蹏」遵循MIT协议开源。

563 | 564 | 580 |
581 |
582 | 583 | 600 | 601 | 602 | 628 | 629 | 630 | -------------------------------------------------------------------------------- /_site/scss/components/_anchor.scss: -------------------------------------------------------------------------------- 1 | // Author: Sivan [sun.sivan@gmail.com] 2 | // Description: anchor. 3 | 4 | .anchor { 5 | margin-inline-start: 0.25em; 6 | } 7 | 8 | @media screen and (min-width: 640px) { 9 | .article { 10 | .anchor { 11 | position: absolute; 12 | left: -1em; 13 | width: 1em; 14 | margin-inline-start: 0; 15 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 16 | font-weight: 400; 17 | line-height: inherit; 18 | text-align: center; 19 | opacity: 0; 20 | transition: opacity 0.2s linear; 21 | 22 | &:hover { 23 | text-decoration: none; 24 | border: 0; 25 | } 26 | } 27 | 28 | h1, 29 | h2, 30 | h3, 31 | h4, 32 | h5, 33 | h6 { 34 | position: relative; 35 | 36 | &:hover { 37 | .anchor { 38 | opacity: 1; 39 | } 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /_site/scss/components/_card.scss: -------------------------------------------------------------------------------- 1 | // Author: Sivan [sun.sivan@gmail.com] 2 | // Description: card. 3 | 4 | .card { 5 | position: relative; 6 | left: -12px; 7 | width: 100%; 8 | margin-block-start: 24px; 9 | margin-block-end: 48px; 10 | padding-block-start: 12px; 11 | padding-block-end: 12px; 12 | padding-inline-start: 12px; 13 | padding-inline-end: 12px; 14 | border-radius: 2px; 15 | box-shadow: 0 4px 16px hsla(0, 0%, 0%, 0.16); 16 | background-color: hsl(0, 0%, 100%); 17 | 18 | // 内容居左,覆盖 .heti figure 19 | .article & { 20 | text-align: left; 21 | } 22 | 23 | > figcaption { 24 | display: inline-block; 25 | margin-block-start: 16px; 26 | padding-block-start: 4px; 27 | padding-block-end: 3px; 28 | padding-inline-start: 0; 29 | padding-inline-end: 72px; 30 | line-height: 24px; 31 | border-block-start: 1px solid hsl(0, 0%, 93%); 32 | } 33 | } 34 | 35 | .card__vertical-container { 36 | box-sizing: border-box; 37 | width: 100%; 38 | height: 30em; 39 | border: 1px solid hsl(0, 0%, 93%); 40 | overflow: auto; 41 | writing-mode: vertical-rl; 42 | } 43 | 44 | @media screen and (min-width: 640px) { 45 | .card { 46 | box-sizing: border-box; 47 | left: -20%; 48 | width: 140%; 49 | padding-block-start: 24px; 50 | padding-block-end: 24px; 51 | padding-inline-start: 32px; 52 | padding-inline-end: 32px; 53 | } 54 | } 55 | 56 | @mixin darkmode { 57 | background-color: hsl(0, 0%, 16%); 58 | > figcaption { 59 | border-block-start: 1px solid hsl(0, 0%, 20%); 60 | } 61 | .card__vertical-container { 62 | border-color: hsl(0, 0%, 20%); 63 | } 64 | } 65 | 66 | .card { 67 | [data-darkmode="dark"] & { 68 | @include darkmode(); 69 | } 70 | @media (prefers-color-scheme: dark) { 71 | [data-darkmode="auto"] & { 72 | @include darkmode(); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /_site/scss/components/_grid-container.scss: -------------------------------------------------------------------------------- 1 | // Author: Sivan [sun.sivan@gmail.com] 2 | // Description: grid container. 3 | $grid-sizes: ( 4 | "12": 12px, 5 | "24": 24px 6 | ); 7 | $padding-mobile: 12px; 8 | $padding-desktop: 48px; 9 | 10 | @mixin grid-image-horizontal($grid-width, $padding-width, $grid-color: hsl(0, 0%, 93%), $margin-color: $grid-color) { 11 | background-image: 12 | linear-gradient(transparent ($grid-width - 1px), $grid-color 1px), 13 | linear-gradient(to left, transparent $padding-width, $grid-color, transparent ($padding-width + 1px)), 14 | linear-gradient(to right, transparent $padding-width, $margin-color, transparent ($padding-width + 1px)); 15 | } 16 | 17 | @mixin grid-image-vertical($grid-width, $grid-color: hsl(0, 0%, 93%)) { 18 | background-size: $grid-width 100%; 19 | background-image: linear-gradient(to left, transparent ($grid-width - 1px), $grid-color 1px); 20 | } 21 | 22 | @mixin darkmode-mobile { 23 | @each $size, $width in $grid-sizes { 24 | &[data-bg-grid="grid-#{$size}"] { 25 | @include grid-image-horizontal($width, $padding-mobile, hsl(0, 0%, 20%)); 26 | 27 | .heti--vertical { 28 | @include grid-image-vertical($width, hsl(0, 0%, 20%)); 29 | } 30 | } 31 | } 32 | } 33 | 34 | @mixin darkmode-desktop { 35 | @each $size, $width in $grid-sizes { 36 | &[data-bg-grid="grid-#{$size}"] { 37 | @include grid-image-horizontal($width, $padding-desktop, hsl(0, 0%, 20%), hsla(360, 100%, 65%, 0.4)); 38 | } 39 | } 40 | } 41 | 42 | .container { 43 | @each $size, $width in $grid-sizes { 44 | &[data-bg-grid="grid-#{$size}"] { 45 | background-size: 100% $width; 46 | 47 | @include grid-image-horizontal($width, $padding-mobile); 48 | 49 | .heti--vertical { 50 | outline: 1px solid hsl(0, 0%, 93%); 51 | 52 | @include grid-image-vertical($width); 53 | } 54 | } 55 | } 56 | 57 | [data-darkmode="dark"] & { 58 | @include darkmode-mobile; 59 | } 60 | 61 | @media (prefers-color-scheme: dark) { 62 | [data-darkmode="auto"] & { 63 | @include darkmode-mobile; 64 | } 65 | } 66 | 67 | @media screen and (min-width: 640px) { 68 | @each $size, $width in $grid-sizes { 69 | &[data-bg-grid="grid-#{$size}"] { 70 | // 桌面端增加缩进,修改边距线为红色 71 | @include grid-image-horizontal($width, $padding-desktop, hsl(0, 0%, 93%), hsla(360, 100%, 50%, 0.4)); 72 | } 73 | } 74 | 75 | [data-darkmode="dark"] & { 76 | @include darkmode-desktop; 77 | } 78 | 79 | @media (prefers-color-scheme: dark) { 80 | [data-darkmode="auto"] & { 81 | @include darkmode-desktop; 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /_site/scss/components/_panel-list.scss: -------------------------------------------------------------------------------- 1 | // Author: Sivan [sun.sivan@gmail.com] 2 | // Description: panel list. 3 | @mixin lightmode { 4 | --bg-color: hsl(240, 100%, 100%); 5 | --bg-tap-color: hsl(300, 3%, 94%); 6 | --border-color: hsla(0, 0%, 76%, 0.88); 7 | --border-inline-start-color: hsl(0, 0%, 76%); 8 | --border-inline-end-color: hsl(0, 0%, 76%); 9 | --border-block-start-color: hsl(0, 0%, 79%); 10 | --border-block-end-color: hsl(0, 0%, 65%); 11 | --text-color: hsl(60, 1%, 16%); 12 | --box-shadow-color: hsla(0, 0%, 86%, 0.54); 13 | --bg-highlight-color-start: hsl(216, 87%, 70%); 14 | --bg-highlight-color-end: hsl(215, 93%, 52%); 15 | --bg-highlight-tap-color-start: hsl(216, 92%, 65%); 16 | --bg-highlight-tap-color-end: hsl(215, 95%, 44%); 17 | --border-highlight-color: hsla(216, 90%, 57%, 0.88); 18 | --border-highlight-tap-color: hsla(216, 85%, 52%, 0.88); 19 | --border-inline-start-highlight-color: hsl(216, 90%, 57%); 20 | --border-inline-end-highlight-color: hsl(216, 90%, 57%); 21 | --border-inline-start-highlight-tap-color: hsl(216, 85%, 52%); 22 | --border-inline-end-highlight-tap-color: hsl(216, 85%, 52%); 23 | --border-block-start-highlight-color: hsl(216, 87%, 63%); 24 | --border-block-end-highlight-color: hsl(215, 99%, 49%); 25 | --border-block-start-highlight-tap-color: hsl(216, 93%, 57%); 26 | --border-block-end-highlight-tap-color: hsl(216, 100%, 42%); 27 | --text-highlight-color: hsl(0, 0%, 100%); 28 | 29 | &--gray { 30 | --bg-color: hsl(180, 100%, 100%); 31 | --bg-tap-color: hsl(0, 0%, 94%); 32 | --border-color: hsla(0, 0%, 72%, 0.88); 33 | --border-inline-start-color: hsl(0, 0%, 76%); 34 | --border-inline-end-color: hsl(0, 0%, 76%); 35 | --border-block-start-color: hsl(0, 0%, 79%); 36 | --border-block-end-color: hsl(0, 0%, 65%); 37 | --text-color: hsl(0, 0%, 16%); 38 | --box-shadow-color: hsla(0, 0%, 86%, 0.54); 39 | --bg-highlight-color-start: hsl(225, 3%, 70%); 40 | --bg-highlight-color-end: hsl(228, 2%, 58%); 41 | --bg-highlight-tap-color-start: hsl(228, 2%, 58%); 42 | --bg-highlight-tap-color-end: hsl(240, 3%, 35%); 43 | --border-highlight-color: hsla(225, 2%, 64%, 0.88); 44 | --border-highlight-tap-color: hsla(228, 2%, 47%, 0.88); 45 | --border-inline-start-highlight-color: hsl(240, 3%, 59%); 46 | --border-inline-end-highlight-color: hsl(228, 2%, 54%); 47 | --border-inline-start-highlight-tap-color: hsl(240, 3%, 39%); 48 | --border-inline-end-highlight-tap-color: hsl(228, 3%, 39%); 49 | --border-block-start-highlight-color: hsl(240, 3%, 66%); 50 | --border-block-end-highlight-color: hsl(228, 2%, 50%); 51 | --border-block-start-highlight-tap-color: hsl(228, 2%, 51%); 52 | --border-block-end-highlight-tap-color: hsl(228, 4%, 26%); 53 | --text-highlight-color: hsl(0, 0%, 92%); 54 | } 55 | } 56 | 57 | @mixin darkmode { 58 | --bg-color: hsl(225, 2%, 40%); 59 | --bg-tap-color: hsl(210, 2%, 49%); 60 | --border-color: hsla(210, 2%, 33%, 0.88); 61 | --border-inline-start-color: hsl(210, 2%, 33%); 62 | --border-inline-end-color: hsl(210, 2%, 33%); 63 | --border-block-start-color: hsl(225, 2%, 45%); 64 | --border-block-end-color: hsl(210, 2%, 40%); 65 | --text-color: hsl(0, 0%, 91%); 66 | --box-shadow-color: hsla(225, 4%, 21%, 0.54); 67 | --bg-highlight-color-start: hsl(216, 77%, 49%); 68 | --bg-highlight-color-end: hsl(216, 76%, 45%); 69 | --bg-highlight-tap-color-start: hsl(214, 83%, 55%); 70 | --bg-highlight-tap-color-end: hsl(215, 74%, 51%); 71 | --border-highlight-color: hsla(215, 77%, 47%, 0.88); 72 | --border-highlight-tap-color: hsla(215, 79%, 54%, 0.88); 73 | --border-inline-start-highlight-color: hsl(215, 77%, 47%); 74 | --border-inline-end-highlight-color: hsl(215, 77%, 47%); 75 | --border-inline-start-highlight-tap-color: hsl(215, 79%, 54%); 76 | --border-inline-end-highlight-tap-color: hsl(215, 79%, 54%); 77 | --border-block-start-highlight-color: hsl(216, 76%, 55%); 78 | --border-block-end-highlight-color: hsl(216, 76%, 44%); 79 | --border-block-start-highlight-tap-color: hsl(215, 84%, 60%); 80 | --border-block-end-highlight-tap-color: hsl(215, 74%, 51%); 81 | --text-highlight-color: hsl(0, 0%, 100%); 82 | 83 | &--gray { 84 | --bg-color: hsl(0, 0%, 40%); 85 | --bg-tap-color: hsl(0, 0%, 49%); 86 | --border-color: hsla(120, 1%, 34%, 0.88); 87 | --border-inline-start-color: hsl(0, 0%, 40%); 88 | --border-inline-end-color: hsl(360, 0%, 40%); 89 | --border-block-start-color: hsl(0, 0%, 46%); 90 | --border-block-end-color: hsl(120, 0%, 40%); 91 | --text-color: hsl(0, 0%, 91%); 92 | --box-shadow-color: hsla(0, 0%, 17%, 0.54); 93 | --bg-highlight-color-start: hsl(0, 0%, 60%); 94 | --bg-highlight-color-end: hsl(0, 0%, 60%); 95 | --bg-highlight-tap-color-start: hsl(0, 0%, 69%); 96 | --bg-highlight-tap-color-end: hsl(0, 0%, 69%); 97 | --border-highlight-color: hsla(0, 0%, 60%, 0.88); 98 | --border-highlight-tap-color: hsla(0, 0%, 69%, 0.88); 99 | --border-inline-start-highlight-color: hsl(0, 0%, 60%); 100 | --border-inline-end-highlight-color: hsl(0, 0%, 60%); 101 | --border-inline-start-highlight-tap-color: hsl(0, 0%, 69%); 102 | --border-inline-end-highlight-tap-color: hsl(0, 0%, 69%); 103 | --border-block-start-highlight-color: hsl(0, 0%, 64%); 104 | --border-block-end-highlight-color: hsl(0, 0%, 60%); 105 | --border-block-start-highlight-tap-color: hsl(0, 0%, 73%); 106 | --border-block-end-highlight-tap-color: hsl(0, 0%, 69%); 107 | --text-highlight-color: hsl(0, 0%, 15%); 108 | } 109 | } 110 | 111 | .panel-list { 112 | display: inline-flex; 113 | margin: 0; 114 | padding: 0; 115 | list-style-type: none; 116 | 117 | @include lightmode; 118 | 119 | [data-darkmode="dark"] & { 120 | @include darkmode; 121 | } 122 | 123 | @media (prefers-color-scheme: dark) { 124 | [data-darkmode="auto"] & { 125 | @include darkmode; 126 | } 127 | } 128 | 129 | & + & { 130 | margin-inline-start: 12px; 131 | } 132 | 133 | label { 134 | position: relative; 135 | display: block; 136 | box-sizing: border-box; 137 | height: 20px; 138 | padding: 0; 139 | padding-inline-start: 12px; 140 | padding-inline-end: 12px; 141 | border: 1px solid; 142 | font-size: 12px; 143 | line-height: 18px; 144 | text-align: center; 145 | user-select: none; 146 | background-color: var(--bg-color); 147 | border-color: var(--border-color); 148 | border-block-start-color: var(--border-block-start-color); 149 | border-block-end-color: var(--border-block-end-color); 150 | color: var(--text-color); 151 | box-shadow: 0 1px 0 var(--box-shadow-color); 152 | 153 | &:active { 154 | background-color: var(--bg-tap-color); 155 | } 156 | } 157 | 158 | li { 159 | margin-inline-end: -1px; 160 | 161 | &:first-child label { 162 | border-top-left-radius: 4px; 163 | border-bottom-left-radius: 4px; 164 | border-inline-start-color: var(--border-inline-start-color); 165 | } 166 | 167 | &:last-child label { 168 | border-top-right-radius: 4px; 169 | border-bottom-right-radius: 4px; 170 | border-inline-end-color: var(--border-inline-end-color); 171 | } 172 | } 173 | 174 | input { 175 | display: none; 176 | 177 | &:checked + label { 178 | z-index: 1; 179 | background-image: linear-gradient(to bottom, var(--bg-highlight-color-start), var(--bg-highlight-color-end)); 180 | border-color: var(--border-highlight-color); 181 | border-block-start-color: var(--border-block-start-highlight-color); 182 | border-block-end-color: var(--border-block-end-highlight-color); 183 | color: var(--text-highlight-color); 184 | 185 | &:active { 186 | background-image: linear-gradient(to bottom, var(--bg-highlight-tap-color-start), var(--bg-highlight-tap-color-end)); 187 | border-color: var(--border-highlight-tap-color); 188 | border-block-start-color: var(--border-block-start-highlight-tap-color); 189 | border-block-end-color: var(--border-block-end-highlight-tap-color); 190 | } 191 | } 192 | } 193 | 194 | &--icon { 195 | label { 196 | width: 30px; 197 | padding-inline-start: 8px; 198 | padding-inline-end: 8px; 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /_site/scss/heti.scss: -------------------------------------------------------------------------------- 1 | $darkmode: 'manual'; 2 | 3 | @import "../../lib/heti"; 4 | -------------------------------------------------------------------------------- /_site/scss/index.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Project: Heti homepage 3 | * URL: https://github.com/sivan/heti 4 | * Author: Sivan [sun.sivan@gmail.com] 5 | */ 6 | @import "lib/normalize"; 7 | @import "lib/reset"; 8 | @import "components/grid-container"; 9 | @import "components/panel-list"; 10 | @import "components/anchor"; 11 | @import "components/card"; 12 | 13 | /** 基础样式 **/ 14 | body { 15 | font-family: -apple-system, system-ui, "Helvetica Neue", Helvetica, Arial, "Heti Hei", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 16 | background-color: hsl(0, 0%, 100%); 17 | } 18 | 19 | a { 20 | color: hsl(217, 89%, 61%); 21 | text-decoration: none; 22 | } 23 | 24 | .container { 25 | box-sizing: border-box; 26 | padding-block-start: 12px; 27 | padding-block-end: 72px; 28 | padding-inline-start: 12px; 29 | padding-inline-end: 12px; 30 | } 31 | 32 | /** 首页样式 **/ 33 | .article { 34 | margin-inline-start: auto; 35 | margin-inline-end: auto; 36 | 37 | // 导航样式 38 | &__nav { 39 | ol { 40 | margin-block-start: 24px; 41 | margin-block-end: 24px; 42 | } 43 | } 44 | } 45 | 46 | /** 控制栏样式 **/ 47 | .panel { 48 | position: fixed; 49 | z-index: 2; 50 | right: 14px; 51 | top: 14px; 52 | display: flex; 53 | text-align: right; 54 | } 55 | 56 | /** 演示区块 **/ 57 | .demo { 58 | margin-block-start: 24px; 59 | margin-block-end: 24px; 60 | } 61 | 62 | .section { 63 | width: 100%; 64 | max-height: 85vh; 65 | margin-block-start: 12px; 66 | margin-block-end: 12px; 67 | overflow: auto; 68 | } 69 | 70 | @media screen and (min-width: 640px) { 71 | body { 72 | min-width: 640px; 73 | background-color: hsl(0, 0%, 93%); 74 | } 75 | 76 | .container { 77 | box-sizing: border-box; 78 | width: 80%; 79 | min-width: 640px; 80 | max-width: 768px; 81 | margin-block-start: 48px; 82 | margin-block-end: 72px; 83 | margin-inline-start: auto; 84 | margin-inline-end: auto; 85 | padding-block-start: 48px; 86 | padding-block-end: 48px; 87 | padding-inline-start: 48px; 88 | padding-inline-end: 48px; 89 | border-radius: 2px; 90 | box-shadow: 0 8px 32px hsla(0, 0%, 0%, 0.32); 91 | background-color: hsl(0, 0%, 100%); 92 | } 93 | 94 | .section { 95 | max-height: none; 96 | overflow: visible; 97 | } 98 | } 99 | 100 | @media screen and (min-width: 900px) { 101 | .article { 102 | &__nav { 103 | position: relative; 104 | z-index: 1; 105 | float: right; 106 | width: 192px; 107 | margin-block-start: -1px; 108 | margin-block-end: 12px; 109 | margin-inline-start: 32px; 110 | margin-inline-end: -16px; 111 | padding-block-start: 12px; 112 | padding-block-end: 12px; 113 | padding-inline-start: 8px; 114 | padding-inline-end: 8px; 115 | border: 1px solid hsl(0, 0%, 93%); 116 | border-radius: 2px; 117 | 118 | ol { 119 | margin-block-start: 12px; 120 | margin-block-end: 0; 121 | 122 | ol { 123 | margin-block-start: 0; 124 | } 125 | } 126 | } 127 | } 128 | } 129 | 130 | @mixin darkmode() { 131 | body { 132 | background-color: hsl(0, 0%, 24%); 133 | } 134 | 135 | a { 136 | color: hsl(217, 49%, 61%); 137 | 138 | &:visited { 139 | color: hsl(217, 49%, 36%); 140 | } 141 | } 142 | 143 | .container { 144 | background-color: hsl(0, 0%, 16%); 145 | color: hsl(0, 0%, 64%); 146 | } 147 | 148 | .article { 149 | &__nav { 150 | border-color: hsl(0, 0%, 20%); 151 | } 152 | } 153 | } 154 | 155 | // Force dark mode 156 | [data-darkmode="dark"] { 157 | @include darkmode(); 158 | } 159 | 160 | @media (prefers-color-scheme: dark) { 161 | [data-darkmode="auto"] { 162 | @include darkmode(); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /_site/scss/lib/_normalize.scss: -------------------------------------------------------------------------------- 1 | /* normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /* Document 4 | ========================================================================== */ 5 | 6 | /** 7 | * 1. Correct the line height in all browsers. 8 | * 2. Prevent adjustments of font size after orientation changes in iOS. 9 | */ 10 | 11 | html { 12 | line-height: 1.15; /* 1 */ 13 | -webkit-text-size-adjust: 100%; /* 2 */ 14 | } 15 | 16 | /* Sections 17 | ========================================================================== */ 18 | 19 | /** 20 | * Remove the margin in all browsers. 21 | */ 22 | 23 | body { 24 | margin: 0; 25 | } 26 | 27 | /** 28 | * Render the `main` element consistently in IE. 29 | */ 30 | 31 | main { 32 | display: block; 33 | } 34 | 35 | /** 36 | * Correct the font size and margin on `h1` elements within `section` and 37 | * `article` contexts in Chrome, Firefox, and Safari. 38 | */ 39 | 40 | h1 { 41 | font-size: 2em; 42 | margin: 0.67em 0; 43 | } 44 | 45 | /* Grouping content 46 | ========================================================================== */ 47 | 48 | /** 49 | * 1. Add the correct box sizing in Firefox. 50 | * 2. Show the overflow in Edge and IE. 51 | */ 52 | 53 | hr { 54 | box-sizing: content-box; /* 1 */ 55 | height: 0; /* 1 */ 56 | overflow: visible; /* 2 */ 57 | } 58 | 59 | /** 60 | * 1. Correct the inheritance and scaling of font size in all browsers. 61 | * 2. Correct the odd `em` font sizing in all browsers. 62 | */ 63 | 64 | pre { 65 | font-family: monospace, monospace; /* 1 */ 66 | font-size: 1em; /* 2 */ 67 | } 68 | 69 | /* Text-level semantics 70 | ========================================================================== */ 71 | 72 | /** 73 | * Remove the gray background on active links in IE 10. 74 | */ 75 | 76 | a { 77 | background-color: transparent; 78 | } 79 | 80 | /** 81 | * 1. Remove the bottom border in Chrome 57- 82 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 83 | */ 84 | 85 | abbr[title] { 86 | border-bottom: none; /* 1 */ 87 | text-decoration: underline; /* 2 */ 88 | text-decoration: underline dotted; /* 2 */ 89 | } 90 | 91 | /** 92 | * Add the correct font weight in Chrome, Edge, and Safari. 93 | */ 94 | 95 | b, 96 | strong { 97 | font-weight: bolder; 98 | } 99 | 100 | /** 101 | * 1. Correct the inheritance and scaling of font size in all browsers. 102 | * 2. Correct the odd `em` font sizing in all browsers. 103 | */ 104 | 105 | code, 106 | kbd, 107 | samp { 108 | font-family: monospace, monospace; /* 1 */ 109 | font-size: 1em; /* 2 */ 110 | } 111 | 112 | /** 113 | * Add the correct font size in all browsers. 114 | */ 115 | 116 | small { 117 | font-size: 80%; 118 | } 119 | 120 | /** 121 | * Prevent `sub` and `sup` elements from affecting the line height in 122 | * all browsers. 123 | */ 124 | 125 | sub, 126 | sup { 127 | font-size: 75%; 128 | line-height: 0; 129 | position: relative; 130 | vertical-align: baseline; 131 | } 132 | 133 | sub { 134 | bottom: -0.25em; 135 | } 136 | 137 | sup { 138 | top: -0.5em; 139 | } 140 | 141 | /* Embedded content 142 | ========================================================================== */ 143 | 144 | /** 145 | * Remove the border on images inside links in IE 10. 146 | */ 147 | 148 | img { 149 | border-style: none; 150 | } 151 | 152 | /* Forms 153 | ========================================================================== */ 154 | 155 | /** 156 | * 1. Change the font styles in all browsers. 157 | * 2. Remove the margin in Firefox and Safari. 158 | */ 159 | 160 | button, 161 | input, 162 | optgroup, 163 | select, 164 | textarea { 165 | font-family: inherit; /* 1 */ 166 | font-size: 100%; /* 1 */ 167 | line-height: 1.15; /* 1 */ 168 | margin: 0; /* 2 */ 169 | } 170 | 171 | /** 172 | * Show the overflow in IE. 173 | * 1. Show the overflow in Edge. 174 | */ 175 | 176 | button, 177 | input { /* 1 */ 178 | overflow: visible; 179 | } 180 | 181 | /** 182 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 183 | * 1. Remove the inheritance of text transform in Firefox. 184 | */ 185 | 186 | button, 187 | select { /* 1 */ 188 | text-transform: none; 189 | } 190 | 191 | /** 192 | * Correct the inability to style clickable types in iOS and Safari. 193 | */ 194 | 195 | button, 196 | [type="button"], 197 | [type="reset"], 198 | [type="submit"] { 199 | -webkit-appearance: button; 200 | } 201 | 202 | /** 203 | * Remove the inner border and padding in Firefox. 204 | */ 205 | 206 | button::-moz-focus-inner, 207 | [type="button"]::-moz-focus-inner, 208 | [type="reset"]::-moz-focus-inner, 209 | [type="submit"]::-moz-focus-inner { 210 | border-style: none; 211 | padding: 0; 212 | } 213 | 214 | /** 215 | * Restore the focus styles unset by the previous rule. 216 | */ 217 | 218 | button:-moz-focusring, 219 | [type="button"]:-moz-focusring, 220 | [type="reset"]:-moz-focusring, 221 | [type="submit"]:-moz-focusring { 222 | outline: 1px dotted ButtonText; 223 | } 224 | 225 | /** 226 | * Correct the padding in Firefox. 227 | */ 228 | 229 | fieldset { 230 | padding: 0.35em 0.75em 0.625em; 231 | } 232 | 233 | /** 234 | * 1. Correct the text wrapping in Edge and IE. 235 | * 2. Correct the color inheritance from `fieldset` elements in IE. 236 | * 3. Remove the padding so developers are not caught out when they zero out 237 | * `fieldset` elements in all browsers. 238 | */ 239 | 240 | legend { 241 | box-sizing: border-box; /* 1 */ 242 | color: inherit; /* 2 */ 243 | display: table; /* 1 */ 244 | max-width: 100%; /* 1 */ 245 | padding: 0; /* 3 */ 246 | white-space: normal; /* 1 */ 247 | } 248 | 249 | /** 250 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 251 | */ 252 | 253 | progress { 254 | vertical-align: baseline; 255 | } 256 | 257 | /** 258 | * Remove the default vertical scrollbar in IE 10+. 259 | */ 260 | 261 | textarea { 262 | overflow: auto; 263 | } 264 | 265 | /** 266 | * 1. Add the correct box sizing in IE 10. 267 | * 2. Remove the padding in IE 10. 268 | */ 269 | 270 | [type="checkbox"], 271 | [type="radio"] { 272 | box-sizing: border-box; /* 1 */ 273 | padding: 0; /* 2 */ 274 | } 275 | 276 | /** 277 | * Correct the cursor style of increment and decrement buttons in Chrome. 278 | */ 279 | 280 | [type="number"]::-webkit-inner-spin-button, 281 | [type="number"]::-webkit-outer-spin-button { 282 | height: auto; 283 | } 284 | 285 | /** 286 | * 1. Correct the odd appearance in Chrome and Safari. 287 | * 2. Correct the outline style in Safari. 288 | */ 289 | 290 | [type="search"] { 291 | -webkit-appearance: textfield; /* 1 */ 292 | outline-offset: -2px; /* 2 */ 293 | } 294 | 295 | /** 296 | * Remove the inner padding in Chrome and Safari on macOS. 297 | */ 298 | 299 | [type="search"]::-webkit-search-decoration { 300 | -webkit-appearance: none; 301 | } 302 | 303 | /** 304 | * 1. Correct the inability to style clickable types in iOS and Safari. 305 | * 2. Change font properties to `inherit` in Safari. 306 | */ 307 | 308 | ::-webkit-file-upload-button { 309 | -webkit-appearance: button; /* 1 */ 310 | font: inherit; /* 2 */ 311 | } 312 | 313 | /* Interactive 314 | ========================================================================== */ 315 | 316 | /* 317 | * Add the correct display in Edge, IE 10+, and Firefox. 318 | */ 319 | 320 | details { 321 | display: block; 322 | } 323 | 324 | /* 325 | * Add the correct display in all browsers. 326 | */ 327 | 328 | summary { 329 | display: list-item; 330 | } 331 | 332 | /* Misc 333 | ========================================================================== */ 334 | 335 | /** 336 | * Add the correct display in IE 10+. 337 | */ 338 | 339 | template { 340 | display: none; 341 | } 342 | 343 | /** 344 | * Add the correct display in IE 10. 345 | */ 346 | 347 | [hidden] { 348 | display: none; 349 | } 350 | -------------------------------------------------------------------------------- /_site/scss/lib/_reset.scss: -------------------------------------------------------------------------------- 1 | /* 简单模拟 css reset */ 2 | * { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | ul, 8 | ol { 9 | list-style: none; 10 | } 11 | 12 | /* 模拟不知道哪里流传出来的垃圾代码 */ 13 | ul, 14 | li { 15 | list-style: none; 16 | } 17 | -------------------------------------------------------------------------------- /js/heti-addon.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Heti add-on v 0.1.0 3 | * Add right spacing between CJK & ANS characters 4 | */ 5 | 6 | import Finder from 'heti-findandreplacedomtext' 7 | 8 | const hasOwn = {}.hasOwnProperty 9 | const HETI_NON_CONTIGUOUS_ELEMENTS = Object.assign({}, Finder.NON_CONTIGUOUS_PROSE_ELEMENTS, { 10 | ins: 1, del: 1, s: 1, a: 1, 11 | }) 12 | const HETI_SKIPPED_ELEMENTS = Object.assign({}, Finder.NON_PROSE_ELEMENTS, { 13 | pre: 1, code: 1, sup: 1, sub: 1, 'heti-spacing': 1, 'heti-close': 1, 14 | }) 15 | const HETI_SKIPPED_CLASS = 'heti-skip' 16 | 17 | // 部分正则表达式修改自 pangu.js https://github.com/vinta/pangu.js 18 | const CJK = '\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30fa\u30fc-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff' 19 | const A = 'A-Za-z\u0080-\u00ff\u0370-\u03ff' 20 | const N = '0-9' 21 | const S = '`~!@#\\$%\\^&\\*\\(\\)-_=\\+\\[\\]{}\\\\\\|;:\'",<.>\\/\\?' 22 | const ANS = `${A}${N}${S}` 23 | const REG_CJK_FULL = `(?<=[${CJK}])( *[${ANS}]+(?: +[${ANS}]+)* *)(?=[${CJK}])` 24 | const REG_CJK_START = `([${ANS}]+(?: +[${ANS}]+)* *)(?=[${CJK}])` 25 | const REG_CJK_END = `(?<=[${CJK}])( *[${ANS}]+(?: +[${ANS}]+)*)` 26 | const REG_CJK_FULL_WITHOUT_LOOKBEHIND = `(?:[${CJK}])( *[${ANS}]+(?: +[${ANS}]+)* *)(?=[${CJK}])` 27 | const REG_CJK_END_WITHOUT_LOOKBEHIND = `(?:[${CJK}])( *[${ANS}]+(?: +[${ANS}]+)*)` 28 | const REG_BD_STOP = `。.,、:;!‼?⁇` 29 | const REG_BD_SEP = `·・‧` 30 | const REG_BD_OPEN = `「『(《〈【〖〔[{` 31 | const REG_BD_CLOSE = `」』)》〉】〗〕]}` 32 | const REG_BD_START = `${REG_BD_OPEN}${REG_BD_CLOSE}` 33 | const REG_BD_END = `${REG_BD_STOP}${REG_BD_OPEN}${REG_BD_CLOSE}` 34 | const REG_BD_HALF_OPEN = `“‘` 35 | const REG_BD_HALF_CLOSE = `”’` 36 | const REG_BD_HALF_START = `${REG_BD_HALF_OPEN}${REG_BD_HALF_CLOSE}` 37 | 38 | class Heti { 39 | constructor (rootSelector) { 40 | let supportLookBehind = true 41 | 42 | try { 43 | new RegExp(`(?<=\d)\d`, 'g').test('') 44 | } catch (err) { 45 | console.info(err.name, '该浏览器尚未实现 RegExp positive lookbehind') 46 | supportLookBehind = false 47 | } 48 | 49 | this.rootSelector = rootSelector || '.heti' 50 | this.REG_FULL = new RegExp(supportLookBehind ? REG_CJK_FULL : REG_CJK_FULL_WITHOUT_LOOKBEHIND, 'g') 51 | this.REG_START = new RegExp(REG_CJK_START, 'g') 52 | this.REG_END = new RegExp(supportLookBehind ? REG_CJK_END : REG_CJK_END_WITHOUT_LOOKBEHIND, 'g') 53 | this.offsetWidth = supportLookBehind ? 0 : 1 54 | this.funcForceContext = function forceContext (el) { 55 | return hasOwn.call(HETI_NON_CONTIGUOUS_ELEMENTS, el.nodeName.toLowerCase()) 56 | } 57 | this.funcFilterElements = function filterElements (el) { 58 | return ( 59 | !(el.classList && el.classList.contains(HETI_SKIPPED_CLASS)) && 60 | !hasOwn.call(HETI_SKIPPED_ELEMENTS, el.nodeName.toLowerCase()) 61 | ) 62 | } 63 | } 64 | 65 | spacingElements (elmList) { 66 | for (let $$root of elmList) { 67 | this.spacingElement($$root) 68 | } 69 | } 70 | 71 | spacingElement ($$elm) { 72 | const commonConfig = { 73 | forceContext: this.funcForceContext, 74 | filterElements: this.funcFilterElements, 75 | } 76 | const getWrapper = function (elementName, classList, text) { 77 | const $$r = document.createElement(elementName) 78 | $$r.className = classList 79 | $$r.textContent = text.trim() 80 | return $$r 81 | } 82 | 83 | Finder($$elm, Object.assign({}, commonConfig, { 84 | find: this.REG_FULL, 85 | replace: portion => getWrapper('heti-spacing', 'heti-spacing-start heti-spacing-end', portion.text), 86 | offset: this.offsetWidth, 87 | })) 88 | 89 | Finder($$elm, Object.assign({}, commonConfig, { 90 | find: this.REG_START, 91 | replace: portion => getWrapper('heti-spacing', 'heti-spacing-start', portion.text), 92 | })) 93 | 94 | Finder($$elm, Object.assign({}, commonConfig, { 95 | find: this.REG_END, 96 | replace: portion => getWrapper('heti-spacing', 'heti-spacing-end', portion.text), 97 | offset: this.offsetWidth, 98 | })) 99 | 100 | Finder($$elm, Object.assign({}, commonConfig, { 101 | find: new RegExp(`([${REG_BD_STOP}])(?=[${REG_BD_START}])|([${REG_BD_OPEN}])(?=[${REG_BD_OPEN}])|([${REG_BD_CLOSE}])(?=[${REG_BD_END}])`,'g'), 102 | replace: portion => getWrapper('heti-adjacent', 'heti-adjacent-half', portion.text), 103 | offset: this.offsetWidth, 104 | })) 105 | 106 | Finder($$elm, Object.assign({}, commonConfig, { 107 | find: new RegExp(`([${REG_BD_SEP}])(?=[${REG_BD_OPEN}])|([${REG_BD_CLOSE}])(?=[${REG_BD_SEP}])`,'g'), 108 | replace: portion => getWrapper('heti-adjacent', 'heti-adjacent-quarter', portion.text), 109 | offset: this.offsetWidth, 110 | })) 111 | 112 | // 使用弯引号的情况下,在停顿符号接弯引号(如「。“」)或弯引号接全角开引号(如“《」)时,间距缩进调整到四分之一 113 | Finder($$elm, Object.assign({}, commonConfig, { 114 | find: new RegExp(`([${REG_BD_STOP}])(?=[${REG_BD_HALF_START}])|([${REG_BD_HALF_OPEN}])(?=[${REG_BD_OPEN}])`,'g'), 115 | replace: portion => getWrapper('heti-adjacent', 'heti-adjacent-quarter', portion.text), 116 | offset: this.offsetWidth, 117 | })) 118 | } 119 | 120 | autoSpacing () { 121 | const callback = () => { 122 | const $$rootList = document.querySelectorAll(this.rootSelector) 123 | 124 | for (let $$root of $$rootList) { 125 | this.spacingElement($$root) 126 | } 127 | } 128 | if (document.readyState === 'complete') setTimeout(callback) 129 | else document.addEventListener('DOMContentLoaded', callback) 130 | } 131 | } 132 | 133 | export default Heti 134 | -------------------------------------------------------------------------------- /lib/_base.scss: -------------------------------------------------------------------------------- 1 | // Author: Sivan [sun.sivan@gmail.com] 2 | // Description: base reset and entry styles. 3 | @import "variables"; 4 | 5 | @mixin hetiBase { 6 | // 清容器浮动 7 | @include clear-float(); 8 | 9 | // 清容器内首尾元素外边距 10 | &, 11 | section, 12 | td { 13 | > *:first-child { 14 | margin-block-start: 0 !important; 15 | } 16 | 17 | > *:last-child { 18 | margin-block-end: 0 !important; 19 | } 20 | } 21 | 22 | // 定义块级元素样式 23 | blockquote { 24 | margin-block-start: $std-block-unit * 0.5; 25 | margin-block-end: $std-block-unit; 26 | margin-inline-start: $std-inline-unit * 2; 27 | margin-inline-end: $std-inline-unit * 2; 28 | padding-block-start: $std-block-unit * 0.5; 29 | padding-block-end: $std-block-unit * 0.5; 30 | padding-inline-start: $std-inline-unit; 31 | padding-inline-end: $std-inline-unit; 32 | //border-radius: 4px; 33 | background-color: hsla(0, 0%, 0%, 0.054); 34 | 35 | @include darkmode-style { 36 | background-color: hsla(0, 0%, 100%, 0.054); 37 | } 38 | } 39 | 40 | figure { 41 | display: block; 42 | text-align: center; 43 | 44 | > img { 45 | display: block; 46 | margin-inline-start: auto; 47 | margin-inline-end: auto; 48 | } 49 | } 50 | 51 | hr { 52 | inline-size: 30%; 53 | block-size: 1px; 54 | margin-block-start: $std-block-unit * 2; 55 | margin-block-end: $std-block-unit * 2 - 1px; 56 | margin-inline-start: auto; 57 | margin-inline-end: auto; 58 | border: 0; 59 | background-color: hsl(0, 0%, 80%); 60 | 61 | @include darkmode-style { 62 | background-color: hsl(0, 0%, 25%); 63 | } 64 | } 65 | 66 | p { 67 | margin-block-start: $std-block-unit * 0.5; 68 | margin-block-end: $std-block-unit; 69 | text-align: justify; 70 | 71 | @include non-cjk-block { 72 | text-align: start; 73 | } 74 | } 75 | 76 | $format_pre_code: true !default; 77 | 78 | @if $format_pre_code { 79 | pre { 80 | margin-block-start: $std-block-unit * 0.5; 81 | margin-block-end: $std-block-unit * 0.5; 82 | margin-inline-start: 0; 83 | margin-inline-end: 0; 84 | padding-block-start: $std-block-unit * 0.5; 85 | padding-block-end: $std-block-unit * 0.5; 86 | padding-inline-start: $std-inline-unit; 87 | padding-inline-end: $std-inline-unit; 88 | overflow: auto; 89 | font-family: $font-family-mono; 90 | white-space: pre; 91 | word-wrap: normal; 92 | border-radius: 4px; 93 | background-color: hsla(0, 0%, 0%, 0.054); 94 | 95 | @include darkmode-style { 96 | background-color: hsla(0, 0%, 100%, 0.054); 97 | } 98 | 99 | code { 100 | margin: 0; 101 | padding: 0; 102 | border: 0; 103 | border-radius: 0; 104 | background-color: transparent; 105 | color: inherit; 106 | } 107 | } 108 | } 109 | 110 | // 非中文时不加间距 111 | letter-spacing: $letter-spacing-medium; 112 | @include non-cjk-block { 113 | letter-spacing: $letter-spacing-normal; 114 | } 115 | 116 | a, 117 | abbr, 118 | code, 119 | heti-spacing, 120 | [lang="en-US"] { 121 | // There should be no leeter-spacing between English characters. 122 | letter-spacing: normal; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /lib/_font.scss: -------------------------------------------------------------------------------- 1 | // Author: Sivan [sun.sivan@gmail.com] 2 | // Description: define font-face. 3 | @import "fonts/hei"; 4 | @import "fonts/song"; 5 | @import "fonts/kai"; 6 | -------------------------------------------------------------------------------- /lib/_heading.scss: -------------------------------------------------------------------------------- 1 | // Author: Sivan [sun.sivan@gmail.com] 2 | // Description: heading styles. 3 | @import "variables"; 4 | 5 | @mixin hetiHeading { 6 | h1, 7 | h2, 8 | h3, 9 | h4, 10 | h5, 11 | h6 { 12 | position: relative; 13 | 14 | // 顶边距默认为一行间距,且不因边距重叠原因减半 15 | // 底边距考虑到亲密性,默认为半行间距 16 | margin: 0; 17 | margin-block-start: $std-block-unit; 18 | margin-block-end: $std-block-unit * 0.5; 19 | font-weight: $font-weight-bold; 20 | } 21 | 22 | h1 { 23 | margin-block-end: $std-block-unit; 24 | font-size: $font-size-h1; 25 | line-height: $line-height-size-h1; 26 | } 27 | 28 | h2 { 29 | font-size: $font-size-h2; 30 | line-height: $line-height-size-h2; 31 | } 32 | 33 | h3 { 34 | font-size: $font-size-h3; 35 | line-height: $line-height-size-h3; 36 | } 37 | 38 | h4 { 39 | font-size: $font-size-h4; 40 | line-height: $line-height-size-h4; 41 | } 42 | 43 | h5 { 44 | font-size: $font-size-h5; 45 | line-height: $line-height-size-h5; 46 | } 47 | 48 | h6 { 49 | font-size: $font-size-h6; 50 | line-height: $line-height-size-h6; 51 | } 52 | 53 | h1, 54 | h2, 55 | h3 { 56 | // 中文大标题增加微小文字间距 57 | letter-spacing: 0.05em; 58 | 59 | // 非中文时不加间距 60 | @include non-cjk-block { 61 | letter-spacing: 0; 62 | } 63 | } 64 | 65 | // 压缩两个标题之间的间距 66 | h1 + h2, 67 | h2 + h3, 68 | h3 + h4, 69 | h4 + h5, 70 | h5 + h6 { 71 | margin-block-start: $std-block-unit * 0.5; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/_inline.scss: -------------------------------------------------------------------------------- 1 | // Author: Sivan [sun.sivan@gmail.com] 2 | // Description: inline element styles. 3 | @import "variables"; 4 | 5 | @mixin hetiInline { 6 | a { 7 | text-decoration: none; 8 | 9 | &:hover { 10 | padding-block-end: 1px; 11 | border-block-end: 1px solid currentColor; 12 | text-decoration: none; 13 | } 14 | } 15 | 16 | abbr[title] { 17 | padding-block-end: 1px; 18 | border-block-end: 1px dotted; 19 | text-decoration: none; 20 | cursor: help; 21 | } 22 | 23 | b, 24 | strong { 25 | font-weight: $font-weight-bold; 26 | } 27 | 28 | code { 29 | margin-inline-start: 0.25em; 30 | margin-inline-end: 0.25em; 31 | font-family: $font-family-mono; 32 | font-size: 0.875em; 33 | } 34 | 35 | dfn { 36 | font-weight: $font-weight-bold; 37 | 38 | // 非中文时不加粗 39 | @include non-cjk-block { 40 | font-weight: $font-weight-normal; 41 | } 42 | } 43 | 44 | em { 45 | font-weight: $font-weight-bold; 46 | } 47 | 48 | // 标题单行时居中,多行时居左 49 | figcaption { 50 | display: inline-block; 51 | vertical-align: top; 52 | font-size: $font-size-small; 53 | text-align: start; 54 | } 55 | 56 | // 显式斜体标签予以保留 57 | i { 58 | font-style: italic; 59 | } 60 | 61 | ins, 62 | u { 63 | padding-block-end: 1px; 64 | border-block-end: 1px solid; 65 | text-decoration: none; 66 | } 67 | 68 | mark { 69 | padding-block-start: 2px; 70 | padding-block-end: 2px; 71 | padding-inline-start: 1px; 72 | padding-inline-end: 1px; 73 | margin-inline-start: 1px; 74 | margin-inline-end: 1px; 75 | background-color: hsla(58, 100%, 50%, 0.88); 76 | color: inherit; 77 | 78 | @include darkmode-style { 79 | background-color: hsla(58, 100%, 15%, 0.88); 80 | } 81 | } 82 | 83 | // 设置引用文本为中文引号 84 | // 默认无论简繁都采用台湾规范,修改只需重新定义变量 `$chinese-quote-set` 即可。 85 | q { 86 | quotes: map-get(map-get($chinese-quote-presets, $chinese-quote-set), "horizontal"); 87 | 88 | @include non-cjk-block { 89 | quotes: initial; 90 | quotes: auto; 91 | } 92 | } 93 | 94 | rt { 95 | font-size: 0.875em; 96 | font-weight: $font-weight-normal; 97 | } 98 | 99 | // 完美 字号 by Sivan 100 | /// 12px * 0.875 => 11px 101 | /// 14px * 0.875 => 12px 102 | /// 16px * 0.875 => 14px 103 | /// 18px * 0.875 => 16px 104 | /// 20px * 0.875 => 18px 105 | small { 106 | font-size: 0.875em; 107 | } 108 | 109 | strong { 110 | font-weight: $font-weight-bold; 111 | } 112 | 113 | sub, 114 | sup { 115 | position: relative; 116 | margin-inline-start: 0.25em; 117 | margin-inline-end: 0.25em; 118 | font-size: 0.75em; 119 | font-family: $font-family-hei; 120 | font-style: normal; 121 | line-height: 1; 122 | vertical-align: baseline; 123 | } 124 | 125 | sub { 126 | bottom: -0.25em; 127 | } 128 | 129 | sup { 130 | top: -0.5em; 131 | 132 | &:target, 133 | a:target { 134 | background-color: hsl(210, 100%, 93%); 135 | 136 | @include darkmode-style { 137 | background-color: hsl(210, 40%, 38%); 138 | } 139 | } 140 | } 141 | 142 | summary { 143 | padding-inline-start: 1em; 144 | outline: 0; 145 | cursor: pointer; 146 | } 147 | 148 | summary::-webkit-details-marker { 149 | width: 0.6em; 150 | margin-inline-end: 0.4em; 151 | } 152 | 153 | u[title] { 154 | cursor: help; 155 | border-block-end-width: 3px; 156 | border-block-end-style: double; 157 | border-block-end-color: hsla(0, 0%, 0%, 0.54); 158 | 159 | @include darkmode-style { 160 | border-block-end-color: hsla(0, 0%, 100%, 0.54); 161 | } 162 | } 163 | 164 | // 默认禁用中文斜体 https://www.zhihu.com/question/20120243 165 | address, 166 | cite, 167 | dfn, 168 | dt, 169 | em { 170 | font-style: normal; 171 | 172 | // 非中文时显示斜体 173 | @include non-cjk-block { 174 | font-style: italic; 175 | } 176 | } 177 | 178 | // 为带划线的元素添加间距,以防止视觉上混为一个元素 179 | // 注: 如果设成为两个相连元素添加间距,会有一个问题: 180 | // 如果结构是 `倚天屠龙`,「屠龙」前面仍然会有边距。 181 | // 此处跟预期不一致,应该只在两个同名元素紧邻时增加边距,即:`倚天剑屠龙刀` 182 | //@each $tag in (abbr[title], del, ins, s, u) { 183 | // #{$tag} + #{$tag} { 184 | // margin-inline-start: 0.125em; 185 | // } 186 | //} 187 | // 因此采用下面的形式,为所有加划线的元素增加缝隙 188 | abbr[title], 189 | del, 190 | ins, 191 | s, 192 | u { 193 | margin-inline-start: 1px; 194 | margin-inline-end: 1px; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /lib/_list.scss: -------------------------------------------------------------------------------- 1 | // Author: Sivan [sun.sivan@gmail.com] 2 | // Description: list styles. 3 | @import "variables"; 4 | 5 | @mixin hetiList { 6 | // 标准化间距 7 | ul, 8 | ol, 9 | dl { 10 | margin-block-start: $line-height-size-normal * 0.5; 11 | margin-block-end: $line-height-size-normal; 12 | } 13 | 14 | ul, 15 | ol { 16 | padding-inline-start: $text-indent-size; 17 | 18 | ul, 19 | ol { 20 | margin-block-start: 0; 21 | margin-block-end: 0; 22 | } 23 | } 24 | 25 | // 兼容性处理 26 | /// 重置部分 CSS Reset 中 ul, ol { list-style: none; } 造成的样式污染 27 | /// 如果搭配 normalize.css 使用,则不存在这些样式污染 28 | @if $_css-reset-scheme == "reset" { 29 | ul { 30 | list-style-type: disc; 31 | } 32 | 33 | ol { 34 | list-style-type: decimal; 35 | } 36 | 37 | ul ul, 38 | ol ul { 39 | list-style-type: circle; 40 | } 41 | 42 | ul ul ul, 43 | ul ol ul, 44 | ol ul ul, 45 | ol ol ul { 46 | list-style-type: square; 47 | } 48 | 49 | // 重置不知道哪里散播出来的垃圾代码 ul, li { list-style: none; } 50 | li { 51 | list-style-type: unset; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/_table.scss: -------------------------------------------------------------------------------- 1 | // Author: Sivan [sun.sivan@gmail.com] 2 | // Description: table styles. 3 | @import "variables"; 4 | 5 | @mixin hetiTable { 6 | table { 7 | box-sizing: border-box; 8 | table-layout: fixed; 9 | margin-block-start: $std-block-unit * 0.5; 10 | margin-block-end: $std-block-unit; 11 | margin-inline-start: auto; 12 | margin-inline-end: auto; 13 | border-collapse: collapse; 14 | border-width: 1px; 15 | border-style: solid; 16 | border-color: hsl(0, 0%, 80%); 17 | word-break: break-word; 18 | 19 | @include darkmode-style { 20 | border-color: hsl(0, 0%, 25%); 21 | } 22 | } 23 | 24 | th, 25 | td { 26 | padding-block-start: $std-block-unit * 0.25; 27 | padding-block-end: $std-block-unit * 0.25; 28 | padding-inline-start: $std-inline-unit * 0.5; 29 | padding-inline-end: $std-inline-unit * 0.5; 30 | border-width: 1px; 31 | border-style: solid; 32 | border-color: hsl(0, 0%, 80%); 33 | 34 | @include darkmode-style { 35 | border-color: hsl(0, 0%, 25%); 36 | } 37 | } 38 | 39 | caption { 40 | caption-side: bottom; 41 | margin-block-start: 2px; 42 | margin-block-end: -4px; 43 | font-size: $font-size-small; 44 | line-height: $line-height-size-small; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/_variables.scss: -------------------------------------------------------------------------------- 1 | // Author: Sivan [sun.sivan@gmail.com] 2 | // Description: define variables, alias etc. 3 | 4 | // 定义赫蹏根 class 名 5 | $root-selector: '.heti' !default; 6 | $darkmode: true !default; // true | false | 'manual' 7 | $manualmode-auto-selector: '[data-darkmode="auto"] &' !default; 8 | $manualmode-dark-selector: '[data-darkmode="dark"] &' !default; 9 | 10 | // 字体 Fonts 11 | // 字体栈 Font Stacks 12 | $_font-stack-sans: "Helvetica Neue", helvetica, arial !default; 13 | $_font-stack-serif: "Times New Roman", times !default; 14 | $_font-stack-mono: "SFMono-Regular", consolas, "Liberation Mono", menlo, courier !default; 15 | $_font-stack-symbol: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !default; 16 | 17 | // 字体族 Font Families 18 | $font-family-hei: $_font-stack-sans, "Heti Hei", sans-serif, $_font-stack-symbol !default; 19 | $font-family-song: $_font-stack-serif, "Heti Song", serif, $_font-stack-symbol !default; 20 | $font-family-kai: $_font-stack-serif, "Heti Kai", serif, $_font-stack-symbol !default; 21 | $font-family-hei-black: $_font-stack-sans, "Heti Hei Black", sans-serif, $_font-stack-symbol !default; 22 | $font-family-song-black: $_font-stack-serif, "Heti Song Black", serif, $_font-stack-symbol !default; 23 | $font-family-kai-black: $_font-stack-serif, "Heti Kai Black", serif, $_font-stack-symbol !default; 24 | $font-family-mono: $_font-stack-mono, monospace, $_font-stack-symbol !default; 25 | 26 | // 字重 Font Weights 27 | $font-weight-bolder: 800 !default; 28 | $font-weight-bold: 600 !default; 29 | $font-weight-normal: 400 !default; 30 | $font-weight-lighter: 200 !default; 31 | 32 | // 字号 Font Sizes 33 | $font-size-normal: 16px !default; 34 | $font-size-x-large: 20px !default; 35 | $font-size-large: 18px !default; 36 | $font-size-small: 14px !default; 37 | $font-size-x-small: 12px !default; 38 | 39 | $font-size-h1: 32px !default; 40 | $font-size-h2: 24px !default; 41 | $font-size-h3: 20px !default; 42 | $font-size-h4: 18px !default; 43 | $font-size-h5: 16px !default; 44 | $font-size-h6: 14px !default; 45 | 46 | // 行 Lines 47 | // 行宽 48 | $line-length: 42em !default; 49 | 50 | // 行高 Line Heights 51 | $line-height-normal: 1.5 !default; 52 | //$line-height-expanded: 1.875 !default; 53 | $line-height-expanded-ultra: 2.25 !default; 54 | //$line-height-condensed: 1.25 !default; 55 | //$line-height-condensed-ultra: 1 !default; 56 | 57 | // 字符间距 58 | $letter-spacing-normal: 0 !default; 59 | $letter-spacing-small: 0.01em !default; 60 | $letter-spacing-medium: 0.02em !default; 61 | $letter-spacing-large: 0.05em !default; 62 | 63 | $line-height-size-normal: $font-size-normal * $line-height-normal !default; 64 | $line-height-size-large: $line-height-size-normal !default; 65 | $line-height-size-x-large: $font-size-x-large * $line-height-normal !default; 66 | $line-height-size-small: $line-height-size-normal !default; 67 | $line-height-size-x-small: 18px !default; 68 | $line-height-size-h1: 48px !default; 69 | $line-height-size-h2: 36px !default; 70 | $line-height-size-h3: 36px !default; 71 | $line-height-size-h4: 24px !default; 72 | $line-height-size-h5: 24px !default; 73 | $line-height-size-h6: 24px !default; 74 | 75 | // 段落 Paragraphs 76 | // 标准网格单位变量 Standard Length 77 | // 垂直方向标准单位 = 标准行高 78 | // 水平方向标准单位 = 标准字号 79 | $std-block-unit: $line-height-size-normal !default; 80 | $std-inline-unit: $font-size-normal !default; 81 | 82 | // 示例:缩进单位 = 二倍文字宽度 83 | $text-indent-length: 2em !default; 84 | $text-indent-size: $font-size-normal * 2 !default; 85 | 86 | // 中文引号 Chinese Quote Set 87 | // `cn`:中华人民共和国国家标准——GB/T 15834-2011《标点符号用法》 http://www.moe.gov.cn/ewebeditor/uploadfile/2015/01/13/20150113091548267.pdf 88 | // `tw`:中国台湾地区标准——《重訂標點符號手冊》 https://language.moe.gov.tw/001/Upload/FILES/SITE_CONTENT/M0001/HAU/h6.htm 89 | // `common`:部分中文社区(如知乎)在简体中文里亦采用与中国台湾地区标准一致的规范。 90 | // 注:垂直时浏览器会自动旋转,无需定义为垂直方向的字符。 91 | $chinese-quote-presets: ( 92 | "cn": ( 93 | "horizontal": "“" "”" "‘" "’", 94 | "vertical": "『" "』" "「" "」" 95 | ), 96 | "tw": ( 97 | "horizontal": "「" "」" "『" "』", 98 | "vertical": "「" "」" "『" "』" 99 | ), 100 | "common": ( 101 | "horizontal": "「" "」" "『" "』", 102 | "vertical": "「" "」" "『" "』" 103 | ) 104 | ) !default; 105 | 106 | $chinese-quote-set: "common" !default; 107 | 108 | // 栏 Columns 109 | // 分栏 110 | $column-count-list: (1, 2, 3, 4) !default; 111 | $column-width-list: (16em, 20em, 24em, 28em, 32em, 36em, 40em, 44em, 48em) !default; 112 | 113 | // 开发用配置项 Develop Configs 114 | /// 预设重置方案 115 | /// `reset`:假定 Eric Meyer 的 CSS Reset 或其它流行的 Reset 116 | /// `normalize`:指定为 normalize.css 117 | $_css-reset-scheme: "reset"; 118 | 119 | // 混合 Mix-ins 120 | // Mix-in: Clear float 121 | @mixin clear-float { 122 | &::before, 123 | &::after { 124 | content: ""; 125 | display: table; 126 | } 127 | 128 | &::after { 129 | clear: both; 130 | } 131 | } 132 | 133 | // Mix-in: Include Non-cjk styles 134 | @mixin non-cjk-block { 135 | &:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)), 136 | &:not(:lang(zh)) { 137 | @content; 138 | } 139 | } 140 | 141 | // Mix-in: Hang Punctuation Mark 142 | @mixin hang { 143 | position: absolute; 144 | line-height: inherit; 145 | text-indent: 0; 146 | } 147 | 148 | @mixin darkmode-style($darkmode: $darkmode, $dark-selector: $manualmode-dark-selector, $auto-selector: $manualmode-auto-selector) { 149 | // 'manual' darkmode should provide darkmode selector and auto-darkmode selector 150 | @if $darkmode == 'manual' { 151 | #{$dark-selector} { 152 | @content; 153 | } 154 | 155 | @media (prefers-color-scheme: dark) { 156 | #{$auto-selector} { 157 | @content; 158 | } 159 | } 160 | } @else if $darkmode { 161 | @media (prefers-color-scheme: dark) { 162 | @content; 163 | } 164 | } 165 | } 166 | 167 | // 函数 Functions 168 | // Function: batch prefix/suffix list-item 169 | // batch-fix-list((2, 3), '#{$root-selector}--columns-') => (.heti--columns-2, .heti--columns-3) 170 | @function batch-fix-list($list, $prefix: '', $suffix: '') { 171 | $_list: () !default; 172 | 173 | @each $item in $list { 174 | $_list: append($_list, #{$prefix}#{$item}#{$suffix}, comma); 175 | } 176 | 177 | @return $_list; 178 | } 179 | -------------------------------------------------------------------------------- /lib/fonts/_hei.scss: -------------------------------------------------------------------------------- 1 | // Author: Sivan [sun.sivan@gmail.com], Pan RZ [c141028@gmail.com] 2 | // Description: define font-face Heti Hei. 3 | 4 | // 标准 Regular 5 | @font-face { 6 | font-family: "Heti Hei"; 7 | src: 8 | "Heti Hei SC", 9 | "Heti Hei TC", 10 | "Heti Hei JP", 11 | "Heti Hei KR"; 12 | } 13 | 14 | @font-face { 15 | font-family: "Heti Hei SC"; 16 | src: 17 | local("PingFang SC Regular"), 18 | local("Heiti SC Regular"), 19 | local("Microsoft YaHei"), 20 | local("Source Han Sans CN Regular"), 21 | local("Noto Sans CJK SC Regular"), 22 | local("WenQuanYi Micro Hei"), 23 | local("Droid Sans Fallback"); 24 | } 25 | 26 | @font-face { 27 | font-family: "Heti Hei TC"; 28 | src: 29 | local("PingFang TC Regular"), 30 | local("Heiti TC Regular"), 31 | local("Microsoft Jhenghei"), 32 | local("Source Han Sans HK Regular"), 33 | local("Source Han Sans TW Regular"), 34 | local("Noto Sans CJK TC Regular"), 35 | local("WenQuanYi Micro Hei"), 36 | local("Droid Sans Fallback"); 37 | } 38 | 39 | @font-face { 40 | font-family: "Heti Hei JP"; 41 | src: 42 | local("Hiragino Sans GB W3"), 43 | local("Source Han Sans JP Regular"), 44 | local("Noto Sans CJK JP Regular"), 45 | local("Droid Sans Fallback"); 46 | } 47 | 48 | @font-face { 49 | font-family: "Heti Hei KR"; 50 | src: 51 | local("Source Han Sans KR Regular"), 52 | local("Noto Sans CJK KR Regular"), 53 | local("Droid Sans Fallback"); 54 | } 55 | 56 | // 细体 Light 57 | @font-face { 58 | font-family: "Heti Hei"; 59 | font-weight: 200; 60 | src: 61 | "Heti Hei SC Light", 62 | "Heti Hei TC Light", 63 | "Heti Hei JP Light", 64 | "Heti Hei KR Light"; 65 | } 66 | 67 | @font-face { 68 | font-family: "Heti Hei SC Light"; 69 | font-weight: 200; 70 | src: 71 | local("PingFang SC Light"), 72 | local("Heiti SC Light"), 73 | "Heti Hei SC Light Fallback", 74 | local("Source Han Sans CN Light"), 75 | local("Noto Sans CJK SC Light"); 76 | } 77 | 78 | @font-face { 79 | font-family: "Heti Hei TC Light"; 80 | font-weight: 200; 81 | src: 82 | local("PingFang TC Light"), 83 | local("Heiti TC Light"), 84 | local("Microsoft Jhenghei Light"), 85 | local("Source Han Sans HK Light"), 86 | local("Source Han Sans TW Light"), 87 | local("Noto Sans CJK TC Light"); 88 | } 89 | 90 | @font-face { 91 | font-family: "Heti Hei JP Light"; 92 | font-weight: 200; 93 | src: 94 | local("Source Han Sans JP Light"), 95 | local("Noto Sans CJK JP Light"); 96 | } 97 | 98 | @font-face { 99 | font-family: "Heti Hei KR Light"; 100 | font-weight: 200; 101 | src: 102 | local("Source Han Sans KR Light"), 103 | local("Noto Sans CJK KR Light"); 104 | } 105 | 106 | @font-face { 107 | font-family: "Heti Hei SC Light Fallback"; 108 | font-weight: 200; 109 | src: 110 | local("Microsoft YaHei"), 111 | local("Droid Sans Fallback"); 112 | } 113 | 114 | // 粗体 Bold 115 | @font-face { 116 | font-family: "Heti Hei"; 117 | font-weight: 600; 118 | src: 119 | "Heti Hei SC Bold", 120 | "Heti Hei TC Bold", 121 | "Heti Hei JP Bold", 122 | "Heti Hei KR Bold"; 123 | } 124 | 125 | @font-face { 126 | font-family: "Heti Hei SC Bold"; 127 | font-weight: 600; 128 | src: 129 | local("PingFang SC Medium"), 130 | local("Heiti SC Medium"), 131 | "Heti Hei SC Bold Fallback", 132 | local("Source Han Sans CN Bold"), 133 | local("Noto Sans CJK SC Bold"); 134 | } 135 | 136 | @font-face { 137 | font-family: "Heti Hei TC Bold"; 138 | font-weight: 600; 139 | src: 140 | local("PingFang TC Medium"), 141 | local("Heiti TC Medium"), 142 | local("Microsoft Jhenghei Bold"), 143 | local("Source Han Sans HK Bold"), 144 | local("Source Han Sans TW Bold"), 145 | local("Noto Sans CJK TC Bold"); 146 | } 147 | 148 | @font-face { 149 | font-family: "Heti Hei JP Bold"; 150 | font-weight: 600; 151 | src: 152 | local("Hiragino Sans GB W6"), 153 | local("Source Han Sans JP Bold"), 154 | local("Noto Sans CJK JP Bold"); 155 | } 156 | 157 | @font-face { 158 | font-family: "Heti Hei KR Bold"; 159 | font-weight: 600; 160 | src: 161 | local("Source Han Sans KR Bold"), 162 | local("Noto Sans CJK KR Bold"); 163 | } 164 | 165 | @font-face { 166 | font-family: "Heti Hei SC Bold Fallback"; 167 | font-weight: 600; 168 | src: 169 | local("Microsoft YaHei"), 170 | local("Droid Sans Fallback"); 171 | } 172 | 173 | // 黑体 Black 174 | @font-face { 175 | font-family: "Heti Hei Black"; 176 | font-weight: 800; 177 | src: 178 | "Heti Hei SC Black", 179 | "Heti Hei TC Black", 180 | "Heti Hei JP Black", 181 | "Heti Hei KR Black"; 182 | } 183 | 184 | @font-face { 185 | font-family: "Heti Hei SC Black"; 186 | font-weight: 800; 187 | src: 188 | local("Lantinghei SC Heavy"), 189 | local("PingFang SC Semibold"), 190 | local("Heiti SC Medium"), 191 | "Heti Hei SC Black Fallback", 192 | local("Source Han Sans CN Heavy"), 193 | local("Noto Sans CJK SC Heavy"); 194 | } 195 | 196 | @font-face { 197 | font-family: "Heti Hei TC Black"; 198 | font-weight: 800; 199 | src: 200 | local("Lantinghei TC Heavy"), 201 | local("PingFang TC Semibold"), 202 | local("Heiti TC Medium"), 203 | local("Microsoft Jhenghei Bold"), 204 | local("Source Han Sans HK Heavy"), 205 | local("Source Han Sans TW Heavy"), 206 | local("Noto Sans CJK TC Heavy"); 207 | } 208 | 209 | @font-face { 210 | font-family: "Heti Hei JP Black"; 211 | font-weight: 800; 212 | src: 213 | local("Hiragino Sans GB W6"), 214 | local("Source Han Sans JP Heavy"), 215 | local("Noto Sans CJK JP Heavy"); 216 | } 217 | 218 | @font-face { 219 | font-family: "Heti Hei KR Black"; 220 | font-weight: 800; 221 | src: 222 | local("Source Han Sans KR Heavy"), 223 | local("Noto Sans CJK KR Heavy"); 224 | } 225 | 226 | @font-face { 227 | font-family: "Heti Hei SC Black Fallback"; 228 | font-weight: 800; 229 | src: 230 | local("Microsoft YaHei"), 231 | local("Droid Sans Fallback"); 232 | } 233 | -------------------------------------------------------------------------------- /lib/fonts/_kai.scss: -------------------------------------------------------------------------------- 1 | // Author: Sivan [sun.sivan@gmail.com], Pan RZ [c141028@gmail.com] 2 | // Description: define font-face Heti Kai. 3 | 4 | // 标准 Regular 5 | @font-face { 6 | font-family: "Heti Kai"; 7 | src: 8 | local("Kaiti SC Regular"), 9 | local("Kaiti TC Regular"), 10 | local("STKaiti"), 11 | local("Kaiti"), 12 | local("BiauKai"); 13 | } 14 | 15 | // 粗体 Bold 16 | @font-face { 17 | font-family: "Heti Kai"; 18 | font-weight: 600; 19 | src: 20 | local("Kaiti SC Bold"), 21 | local("Kaiti TC Bold"); 22 | } 23 | 24 | @font-face { 25 | font-family: "Heti Kai Bold Fallback"; 26 | font-weight: 600; 27 | src: 28 | local("STKaiti"), 29 | local("Kaiti") 30 | local("BiauKai"); 31 | } 32 | 33 | // 黑体 Black 34 | @font-face { 35 | font-family: "Heti Kai Black"; 36 | font-weight: 800; 37 | src: 38 | local("Kaiti SC Black"), 39 | local("Kaiti TC Black"), 40 | local("STKaiti"), 41 | local("Kaiti"); 42 | } 43 | -------------------------------------------------------------------------------- /lib/fonts/_song.scss: -------------------------------------------------------------------------------- 1 | // Author: Sivan [sun.sivan@gmail.com], Pan RZ [c141028@gmail.com] 2 | // Description: define font-face Heti Song. 3 | 4 | // 标准 Regular 5 | @font-face { 6 | font-family: "Heti Song"; 7 | src: 8 | local("Songti SC Regular"), 9 | local("Songti TC Regular"), 10 | local("SimSun"); 11 | } 12 | 13 | // 细体 Light 14 | @font-face { 15 | font-family: "Heti Song"; 16 | font-weight: 200; 17 | src: 18 | local("Songti SC Light"), 19 | local("Songti TC Light"), 20 | "Heti Song Light Fallback"; 21 | } 22 | 23 | @font-face { 24 | font-family: "Heti Song Light Fallback"; 25 | font-weight: 200; 26 | src: local("SimSun"); 27 | } 28 | 29 | // 粗体 Bold 30 | @font-face { 31 | font-family: "Heti Song"; 32 | font-weight: 600; 33 | src: 34 | local("Songti SC Bold"), 35 | local("Songti TC Bold"), 36 | "Heti Song Bold Fallback"; 37 | } 38 | 39 | @font-face { 40 | font-family: "Heti Song Bold Fallback"; 41 | font-weight: 600; 42 | src: local("SimSun"); 43 | } 44 | 45 | // 黑体 Black 46 | // 只支持简体 47 | @font-face { 48 | font-family: "Heti Song Black"; 49 | font-weight: 800; 50 | src: 51 | local("Songti SC Black"), 52 | // local("Songti TC Black"), 53 | local("SimSun"); 54 | } 55 | -------------------------------------------------------------------------------- /lib/helpers/_add-on.scss: -------------------------------------------------------------------------------- 1 | // Author: Sivan [sun.sivan@gmail.com] 2 | // Description: define add-ons. 3 | @import "../variables"; 4 | 5 | @mixin hetiAddOns { 6 | // 用于中西文混排增加边距 7 | heti-spacing { 8 | display: inline; 9 | 10 | & + sup, 11 | & + sub { 12 | margin-inline-start: 0; 13 | } 14 | } 15 | 16 | .heti-spacing-start { 17 | margin-inline-end: 0.25em; 18 | } 19 | 20 | .heti-spacing-end { 21 | margin-inline-start: 0.25em; 22 | } 23 | 24 | heti-adjacent { 25 | display: inline; 26 | text-spacing-trim: space-all; 27 | } 28 | 29 | .heti-adjacent-half { 30 | margin-inline-end: -0.5em; 31 | } 32 | 33 | .heti-adjacent-quarter { 34 | margin-inline-end: -0.25em; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/helpers/_block.scss: -------------------------------------------------------------------------------- 1 | // Author: Sivan [sun.sivan@gmail.com] 2 | // Description: define block helper classes. 3 | @import "../variables"; 4 | 5 | @mixin hetiBlockHelperClasses { 6 | // 段落相关 7 | // 元信息无缩进 8 | #{$root-selector}-meta { 9 | display: block; 10 | text-indent: 0; 11 | } 12 | 13 | // 诗节无缩进,居中显示 14 | #{$root-selector}-verse { 15 | text-align: center; 16 | text-indent: 0; 17 | } 18 | 19 | // 定义扩展字号 20 | #{$root-selector} { 21 | &-large { 22 | font-size: $font-size-large; 23 | line-height: $line-height-size-large; 24 | } 25 | 26 | &-x-large { 27 | font-size: $font-size-x-large; 28 | line-height: $line-height-size-x-large; 29 | letter-spacing: 0.05em; 30 | } 31 | 32 | &-small { 33 | font-size: $font-size-small; 34 | line-height: $line-height-size-small; 35 | } 36 | 37 | &-x-small { 38 | font-size: $font-size-x-small; 39 | line-height: $line-height-size-x-small; 40 | } 41 | } 42 | 43 | // 列表相关 44 | // 定义拉丁字母的有序列表 45 | #{$root-selector}-list-latin { 46 | list-style-type: upper-latin; 47 | 48 | ol { 49 | list-style-type: lower-roman; 50 | 51 | ol { 52 | list-style-type: lower-latin; 53 | } 54 | } 55 | } 56 | 57 | // 定义中文序号的有序列表 58 | #{$root-selector}-list-han { 59 | list-style-type: cjk-ideographic; 60 | 61 | ol { 62 | list-style-type: decimal; 63 | 64 | ol { 65 | list-style-type: decimal-leading-zero; 66 | } 67 | } 68 | } 69 | 70 | // 页脚 71 | #{$root-selector}-fn { 72 | margin-block-start: 59px; 73 | border-block-start: 1px solid; 74 | border-block-start-color: hsl(0, 0%, 80%); 75 | font-size: $font-size-small; 76 | font-family: $font-family-hei; 77 | line-height: $line-height-size-normal; 78 | 79 | @include darkmode-style { 80 | border-block-start-color: hsl(0, 0%, 25%); 81 | } 82 | 83 | ol { 84 | margin-block-start: $std-block-unit * 0.5; 85 | margin-block-end: 0; 86 | } 87 | 88 | li { 89 | &:target { 90 | background-color: hsl(210, 100%, 93%); 91 | 92 | @include darkmode-style { 93 | background-color: hsl(210, 40%, 38%); 94 | } 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/helpers/_inline.scss: -------------------------------------------------------------------------------- 1 | // Author: Sivan [sun.sivan@gmail.com] 2 | // Description: define inline helper classes. 3 | @import "../variables"; 4 | 5 | @mixin hetiInlineHelperClasses { 6 | // 标点悬挂 Punctuation mark hanging 7 | // @todo: 用于标点悬挂用的样式 8 | #{$root-selector}-hang { 9 | @include hang(); 10 | } 11 | 12 | // 强烈着重号 Emphasis Mark 13 | #{$root-selector}-em { 14 | -webkit-text-emphasis: filled circle; 15 | -webkit-text-emphasis-position: under; 16 | text-emphasis: filled circle; 17 | text-emphasis-position: under right; 18 | 19 | @include non-cjk-block { 20 | -webkit-text-emphasis: none; 21 | text-emphasis: none; 22 | } 23 | } 24 | 25 | // 内联 Ruby 26 | // 在非行间注排版中使用 ruby 标签,且不额外占据空间 27 | // 注:使用此样式需按字拆分 () 28 | // https://stackoverflow.com/questions/38680695/adjust-the-vertical-positioning-of-ruby-text/38877801#38877801 29 | #{$root-selector}-ruby { 30 | &--inline { 31 | display: inline-flex; 32 | flex-direction: column-reverse; 33 | height: 1.5em; 34 | vertical-align: top; 35 | 36 | rt { 37 | display: inline; 38 | margin-bottom: -0.25em; 39 | line-height: 1; 40 | text-align: center; 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/heti.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Project: Heti 3 | * URL: https://github.com/sivan/heti 4 | * Author: Sivan [sun.sivan@gmail.com] 5 | */ 6 | @import "font"; 7 | @import "variables"; 8 | @import "base"; 9 | @import "heading"; 10 | @import "list"; 11 | @import "table"; 12 | @import "inline"; 13 | @import "modifiers/font-stack"; 14 | @import "modifiers/writing-mode"; 15 | @import "modifiers/column"; 16 | @import "modifiers/ancient"; 17 | @import "modifiers/annotation"; 18 | @import "helpers/block"; 19 | @import "helpers/inline"; 20 | @import "helpers/add-on"; 21 | 22 | #{$root-selector} { 23 | // 中文每行展示文字(CPL)建议在 30~50 之间,默认 42 24 | max-width: $line-length; 25 | 26 | // 默认字体大小为 16px,行高 1.5 27 | font-size: $font-size-normal; 28 | font-weight: $font-weight-normal; 29 | -webkit-font-smoothing: subpixel-antialiased; 30 | line-height: $line-height-normal; 31 | 32 | // 针对混合英文段落,采取按词折行,长单词通过连词符段行 33 | // https://justmarkup.com/articles/2015-07-31-dealing-with-long-words-in-css/ 34 | overflow-wrap: break-word; 35 | word-wrap: break-word; 36 | hyphens: auto; 37 | 38 | // 自动在中西文间加 1/4 空格(暂无浏览器支持) 39 | //text-spacing: ideograph-alpha; 40 | 41 | // 模块引用顺序 42 | 43 | // 1. 引入各模块 44 | // .heti h1, .heti p, .heti ul 45 | @include hetiBase(); 46 | @include hetiHeading(); 47 | @include hetiList(); 48 | @include hetiTable(); 49 | @include hetiInline(); 50 | 51 | // 2. 定义所有修饰器(需与 .heti 组合使用) 52 | // .heti--sans h1, .heti--vertical h1 etc. 53 | @include hetiFontModifier(); 54 | @include hetiColumnModifier(); 55 | @include hetiWritingModeModifier(); 56 | @include hetiAncientModifier(); 57 | @include hetiAnnotationModifier(); 58 | 59 | // 3. 定义工具类样式(仅在 .heti 内部使用) 60 | // .heti .heti-verse, .heti .heti-hang 61 | @include hetiBlockHelperClasses(); 62 | @include hetiInlineHelperClasses(); 63 | @include hetiAddOns(); 64 | } 65 | -------------------------------------------------------------------------------- /lib/modifiers/ancient.scss: -------------------------------------------------------------------------------- 1 | // Author: Sivan [sun.sivan@gmail.com] 2 | // Description: define ancient styles. 3 | @import "../variables"; 4 | 5 | @mixin hetiAncientModifier { 6 | // 定义古文、古诗样式 7 | &--ancient, 8 | &--poetry { 9 | // 古文、古诗用宋体(覆盖全局字体) 10 | font-family: $font-family-song; 11 | 12 | // 古文标题用楷体(覆盖全局字体) 13 | h1, 14 | h2, 15 | h3, 16 | h4, 17 | h5, 18 | h6 { 19 | font-family: $font-family-kai-black; 20 | font-weight: $font-weight-bolder; 21 | text-align: center; 22 | 23 | // 标题内元信息仅在桌面端采取悬挂处理,且不占据空间影响文字居中 24 | #{$root-selector}-meta { 25 | font-weight: $font-weight-normal; 26 | 27 | @media screen and (min-width: 640px) { 28 | @include hang(); 29 | 30 | display: inline; 31 | margin-block-start: 4px; 32 | margin-inline-start: 8px; 33 | } 34 | } 35 | } 36 | 37 | #{$root-selector}-meta { 38 | line-height: $line-height-size-normal; 39 | text-align: center; 40 | text-indent: 0; 41 | } 42 | } 43 | 44 | &--ancient { 45 | // 古文(文言文、词)首行缩进 46 | p { 47 | text-indent: $text-indent-length; 48 | } 49 | } 50 | 51 | &--poetry { 52 | // 诗节无缩进,居中显示 53 | p { 54 | text-align: center; 55 | text-indent: 0; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/modifiers/annotation.scss: -------------------------------------------------------------------------------- 1 | // Author: Sivan [sun.sivan@gmail.com] 2 | // Description: define interlinear annotation styles. 3 | @import "../variables"; 4 | 5 | @mixin hetiAnnotationModifier { 6 | &--annotation { 7 | // 首行缩进且行间距加大,去除段落间距 8 | p { 9 | margin-block-start: 0; 10 | margin-block-end: 0; 11 | line-height: $line-height-expanded-ultra; 12 | text-indent: $text-indent-length; 13 | } 14 | 15 | // 着重号不应影响行间距,经测试最小可用行高为 1.7 16 | em { 17 | -webkit-text-emphasis: filled circle; 18 | -webkit-text-emphasis-position: under; 19 | text-emphasis: filled circle; 20 | text-emphasis-position: under right; 21 | font-weight: $font-weight-normal; 22 | 23 | @include non-cjk-block { 24 | -webkit-text-emphasis: none; 25 | text-emphasis: none; 26 | } 27 | } 28 | 29 | // 元信息保持间距 30 | #{$root-selector}-meta { 31 | margin-block-start: $std-block-unit * 0.5; 32 | margin-block-end: $std-block-unit; 33 | } 34 | 35 | // ruby 不应影响行间距,经测试最小可用行高为 2 36 | //ruby {} 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/modifiers/column.scss: -------------------------------------------------------------------------------- 1 | // Author: Sivan [sun.sivan@gmail.com] 2 | // Description: define column styles. 3 | @import "../variables"; 4 | 5 | @mixin hetiColumnModifier { 6 | // 定义多栏排版 7 | $selector-list: batch-fix-list(join($column-count-list, $column-width-list), '&--columns-'); 8 | 9 | #{join($selector-list, comma)} { 10 | // 多行时不再设总宽度限制 11 | max-width: none; 12 | column-gap: 2em; 13 | 14 | // 多栏排版时减半段间距 15 | p { 16 | margin-block-start: $std-block-unit * 0.5 * 0.5; 17 | margin-block-end: $std-block-unit * 0.5; 18 | text-indent: $text-indent-length; 19 | } 20 | } 21 | 22 | @each $columns in $column-count-list { 23 | &--columns-#{$columns} { 24 | column-count: #{$columns}; 25 | } 26 | } 27 | 28 | @each $column-width in $column-width-list { 29 | &--columns-#{$column-width} { 30 | column-width: #{$column-width}; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/modifiers/font-stack.scss: -------------------------------------------------------------------------------- 1 | // Author: Sivan [sun.sivan@gmail.com] 2 | // Description: define global font stacks. 3 | @import "../variables"; 4 | 5 | @mixin hetiFontModifier { 6 | // 定义默认采用无衬线字体 7 | // 定义无衬线字体为黑体 8 | &, 9 | &--sans { 10 | font-family: $font-family-hei; 11 | } 12 | 13 | // 定义衬线字体为宋体 14 | &--serif { 15 | font-family: $font-family-song; 16 | } 17 | 18 | // 定义传统字体 19 | // 「传统字体」仅供设计参考。主旨为标题用楷体,正文用宋体,表头等客观指标描述场合用黑体。 20 | // 由于渲染机制等原因,应尽量避免衬线、非衬线字体混排,会引起行高不一致的bug。 21 | &--classic { 22 | // 正文使用宋体 23 | font-family: $font-family-song; 24 | 25 | // 标题使用楷体 800 字重 26 | h1, 27 | h2, 28 | h3, 29 | h4, 30 | h5, 31 | h6 { 32 | font-family: $font-family-kai-black; 33 | font-weight: $font-weight-bolder; 34 | } 35 | 36 | // 引用使用楷体 37 | blockquote, 38 | cite, 39 | q { 40 | font-family: $font-family-kai; 41 | } 42 | 43 | // 说明文字、表头等反应客观指标、事物的位置使用黑体 44 | figcaption, 45 | caption, 46 | th { 47 | font-family: $font-family-hei; 48 | } 49 | } 50 | 51 | &--hei { 52 | font-family: $font-family-hei; 53 | } 54 | 55 | &--song { 56 | font-family: $font-family-song; 57 | } 58 | 59 | &--kai { 60 | font-family: $font-family-kai; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/modifiers/writing-mode.scss: -------------------------------------------------------------------------------- 1 | // Author: Sivan [sun.sivan@gmail.com] 2 | // Description: define writing mode styles. 3 | @import "../variables"; 4 | 5 | @mixin hetiWritingModeModifier { 6 | // 定义垂直布局 7 | &--vertical { 8 | max-width: none; 9 | max-height: $line-length; 10 | writing-mode: vertical-rl; 11 | letter-spacing: 0.125em; 12 | 13 | h1, 14 | h2, 15 | h3, 16 | h4, 17 | h5, 18 | h6 { 19 | text-align: start; 20 | } 21 | 22 | // 默认无论简繁都采用台湾规范,修改只需重新定义变量 `$chinese-quote-set` 即可。 23 | q { 24 | quotes: map-get(map-get($chinese-quote-presets, $chinese-quote-set), "vertical"); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "heti", 3 | "version": "0.9.5", 4 | "description": "赫蹏是专为中文内容展示设计的排版样式增强。它基于通行的中文排版规范而来,可以为网站的读者带来更好的文章阅读体验。", 5 | "main": "lib/heti.scss", 6 | "files": [ 7 | "umd", 8 | "js", 9 | "lib" 10 | ], 11 | "scripts": { 12 | "start": "sass -w --no-source-map _site/scss/:_site/", 13 | "compile": "rollup -c -w", 14 | "build:style": "sass --no-source-map --style=compressed lib/heti.scss:umd/heti.min.css", 15 | "build:site": "sass --no-source-map _site/scss/:_site/", 16 | "build:script": "rollup -c", 17 | "build": "npm run build:style && npm run build:site && npm run build:script", 18 | "test": "npx stylelint --config package.json 'lib/**/*.scss'" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/sivan/heti.git" 23 | }, 24 | "keywords": [ 25 | "typography", 26 | "clreq", 27 | "css", 28 | "sass", 29 | "scss" 30 | ], 31 | "author": "Sivan ", 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/sivan/heti/issues" 35 | }, 36 | "homepage": "https://github.com/sivan/heti#readme", 37 | "dependencies": { 38 | "heti-findandreplacedomtext": "^0.5.0" 39 | }, 40 | "devDependencies": { 41 | "@rollup/plugin-commonjs": "^11.1.0", 42 | "@rollup/plugin-node-resolve": "^7.1.3", 43 | "rollup": "^1.32.1", 44 | "rollup-plugin-terser": "^5.3.1", 45 | "sass": "^1.57.0", 46 | "stylelint": "^13.13.1", 47 | "stylelint-config-recommended-scss": "^4.3.0", 48 | "stylelint-config-standard": "^22.0.0", 49 | "stylelint-scss": "^3.21.0" 50 | }, 51 | "stylelint": { 52 | "extends": [ 53 | "stylelint-config-standard", 54 | "stylelint-config-recommended-scss" 55 | ], 56 | "rules": { 57 | "no-descending-specificity": null, 58 | "at-rule-empty-line-before": null, 59 | "block-opening-brace-space-before": "always", 60 | "block-closing-brace-newline-after": [ 61 | "always", 62 | { 63 | "ignoreAtRules": [ 64 | "if", 65 | "else" 66 | ] 67 | } 68 | ], 69 | "property-no-unknown": null, 70 | "rule-empty-line-before": null, 71 | "selector-type-no-unknown": [ 72 | true, 73 | { 74 | "ignore": [ 75 | "custom-elements" 76 | ] 77 | } 78 | ] 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from '@rollup/plugin-commonjs'; 2 | import resolve from '@rollup/plugin-node-resolve'; 3 | import {terser} from 'rollup-plugin-terser'; 4 | 5 | export default { 6 | input: 'js/heti-addon.js', 7 | output: [ 8 | { 9 | file: '_site/heti-addon.js', 10 | name: 'Heti', 11 | format: 'umd' 12 | }, 13 | { 14 | file: 'umd/heti-addon.min.js', 15 | format: 'umd', 16 | name: 'Heti', 17 | plugins: [ 18 | terser({ 19 | compress: { 20 | pure_funcs: ["console.info"] // 移除调试信息 21 | }, 22 | output: { 23 | comments: false 24 | } 25 | }) 26 | ] 27 | }, 28 | ], 29 | plugins: [ 30 | resolve(), 31 | commonjs(), 32 | ] 33 | }; 34 | --------------------------------------------------------------------------------