Compare commits

...

No commits in common. "gh-pages" and "master" have entirely different histories.

47 changed files with 8230 additions and 103 deletions

14
.editorconfig Normal file
View file

@ -0,0 +1,14 @@
# EditorConfig from https://github.com/sivan/dotfiles
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
indent_size = 4
trim_trailing_whitespace = false

1
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1 @@
open_collective: heti

53
.github/workflows/deploy.yml vendored Normal file
View file

@ -0,0 +1,53 @@
name: deploy
on:
push:
branches:
- master
jobs:
build-and-deploy:
runs-on: ubuntu-latest
needs: check-env
steps:
- name: Checkout
uses: actions/checkout@v2
with:
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: 18
- name: NPM Install and Test
run: |
npm install
npm run test
npm run build
- name: Publish
if: needs.check-env.outputs.check-npm == 'true'
uses: JS-DevTools/npm-publish@v1
with:
token: ${{ secrets.NPM_TOKEN }}
- name: Deploy to gh-pages
if: needs.check-env.outputs.check-access == 'true'
uses: JamesIves/github-pages-deploy-action@releases/v3
with:
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
BRANCH: gh-pages
FOLDER: _site
check-env:
runs-on: ubuntu-latest
outputs:
check-npm: ${{ steps.check-npm.outputs.defined }}
check-access: ${{ steps.check-access.outputs.defined }}
steps:
- id: check-npm
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
if: ${{ env.NPM_TOKEN != '' }}
run: echo "::set-output name=defined::true"
- id: check-access
env:
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
if: ${{ env.ACCESS_TOKEN != '' }}
run: echo "::set-output name=defined::true"

31
.github/workflows/nodejs.yml vendored Normal file
View file

@ -0,0 +1,31 @@
---
name: Node CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-20.04
strategy:
matrix:
node-version: [16.x, 18.x]
steps:
- uses: actions/checkout@v1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: npm install and test
run: |
npm ci
npm test
env:
CI: true
- name: npm build
run: npm run build
env:
CI: true

25
.gitignore vendored Normal file
View file

@ -0,0 +1,25 @@
# Editor generate files
.idea/
.settings/
._project/
# Dev dependencies and cache files
node_modules/
npm-debug.log
# Folder view configuration files
.DS_Store
Desktop.ini
# Thumbnail cache files
*~
._*
Thumbs.db
# Files that might appear on external disks
.Spotlight-V100
.Trashes
# Dist files
dist/
umd/

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Sivan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

60
README.md Normal file
View file

@ -0,0 +1,60 @@
# 赫蹏
赫蹏(hètí)是专为中文内容展示设计的排版样式增强。它基于通行的中文排版规范而来,可以为网站的读者带来更好的文章阅读体验。
预览:[https://sivan.github.io/heti/](https://sivan.github.io/heti/)
![Preview](https://raw.githubusercontent.com/sivan/heti/master/_site/assets/screenshot-grid.png)
主要特性:
- 贴合网格的排版;
- 全标签样式美化;
- 预置古文、诗词样式;
- 预置多种排版样式(行间注、多栏、竖排等);
- 多种预设字体族(仅限桌面端);
- 简/繁体中文支持;
- 自适应黑暗模式;
- 中西文混排美化,不再手敲空格👏(基于 JavaScript 脚本);
- 全角标点挤压(基于 JavaScript 脚本);
- 兼容 *normalize.css*、*CSS Reset* 等常见样式重置;
- 移动端支持;
- ……
总之,用上就会变好看。
## 使用方法
1. 在页面的 `<head>` 标签中引入 `heti.css` 文件:
```
<link rel="stylesheet" href="//unpkg.com/heti/umd/heti.min.css">
```
1. 在要作用的容器元素上增加 `class="heti"` 的类名即可:
```
<article class="entry heti">
<h1>我的世界观</h1>
<p>有钱人的生活就是这么朴实无华,且枯燥</p>
……
</article>
```
1. 使用增强脚本(可选):
```
<script src="//unpkg.com/heti/umd/heti-addon.min.js"></script>
<script>
const heti = new Heti('.heti');
heti.autoSpacing(); // 自动进行中西文混排美化和标点挤压
</script>
```
## WIP
暂时没什么想做的了。
- [x] 自适应黑暗模式
- [x] 标点挤压
- [x] 中、西文混排
- [x] 繁体中文支持
- [x] 诗词版式
- [x] 行间注版式
-- EOF --

View file

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

View file

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 132 KiB

View file

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 133 KiB

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View file

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View file

@ -685,7 +685,7 @@
const REG_BD_HALF_START = `${REG_BD_HALF_OPEN}${REG_BD_HALF_CLOSE}`;
class Heti {
constructor (rootSelector) {
constructor (rootDocument, rootSelector) {
let supportLookBehind = true;
try {
@ -695,6 +695,7 @@
supportLookBehind = false;
}
this.rootDocument = rootDocument;
this.rootSelector = rootSelector || '.heti';
this.REG_FULL = new RegExp(supportLookBehind ? REG_CJK_FULL : REG_CJK_FULL_WITHOUT_LOOKBEHIND, 'g');
this.REG_START = new RegExp(REG_CJK_START, 'g');
@ -722,8 +723,8 @@
forceContext: this.funcForceContext,
filterElements: this.funcFilterElements,
};
const getWrapper = function (elementName, classList, text) {
const $$r = document.createElement(elementName);
const getWrapper = (elementName, classList, text) => {
const $$r = this.rootDocument.createElement(elementName);
$$r.className = classList;
$$r.textContent = text.trim();
return $$r
@ -768,14 +769,18 @@
autoSpacing () {
const callback = () => {
const $$rootList = document.querySelectorAll(this.rootSelector);
const $$rootList = this.rootDocument.querySelectorAll(this.rootSelector);
for (let $$root of $$rootList) {
this.spacingElement($$root);
}
};
if (document.readyState === 'complete') setTimeout(callback);
else document.addEventListener('DOMContentLoaded', callback);
if (this.rootDocument == document) {
if (this.rootDocument.readyState === 'complete') setTimeout(callback);
else this.rootDocument.addEventListener('DOMContentLoaded', callback);
} else {
callback();
}
}
}

View file

@ -167,7 +167,6 @@
font-size: 16px;
font-weight: 400;
-webkit-font-smoothing: subpixel-antialiased;
line-height: 1.5;
overflow-wrap: break-word;
word-wrap: break-word;
hyphens: auto;
@ -190,25 +189,6 @@
.heti td > *:last-child {
margin-block-end: 0 !important;
}
.heti blockquote {
margin-block-start: 12px;
margin-block-end: 24px;
margin-inline-start: 32px;
margin-inline-end: 32px;
padding-block-start: 12px;
padding-block-end: 12px;
padding-inline-start: 16px;
padding-inline-end: 16px;
background-color: hsla(0deg, 0%, 0%, 0.054);
}
[data-darkmode=dark] .heti blockquote {
background-color: hsla(0deg, 0%, 100%, 0.054);
}
@media (prefers-color-scheme: dark) {
[data-darkmode=auto] .heti blockquote {
background-color: hsla(0deg, 0%, 100%, 0.054);
}
}
.heti figure {
display: block;
text-align: center;
@ -221,8 +201,8 @@
.heti hr {
width: 30%;
height: 1px;
margin-block-start: 48px;
margin-block-end: 47px;
margin-block-start: 32px;
margin-block-end: 31px;
margin-inline-start: auto;
margin-inline-end: auto;
border: 0;
@ -237,45 +217,13 @@
}
}
.heti p {
margin-block-start: 12px;
margin-block-end: 24px;
margin-block-start: 8px;
margin-block-end: 16px;
text-align: justify;
}
.heti p:not(:lang(zh)):not(:lang(ja)):not(:lang(kr)), .heti p:not(:lang(zh)) {
text-align: start;
}
.heti pre {
margin-block-start: 12px;
margin-block-end: 12px;
margin-inline-start: 0;
margin-inline-end: 0;
padding-block-start: 12px;
padding-block-end: 12px;
padding-inline-start: 16px;
padding-inline-end: 16px;
overflow: auto;
font-family: "SFMono-Regular", consolas, "Liberation Mono", menlo, courier, monospace, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
white-space: pre;
word-wrap: normal;
border-radius: 4px;
background-color: hsla(0deg, 0%, 0%, 0.054);
}
[data-darkmode=dark] .heti pre {
background-color: hsla(0deg, 0%, 100%, 0.054);
}
@media (prefers-color-scheme: dark) {
[data-darkmode=auto] .heti pre {
background-color: hsla(0deg, 0%, 100%, 0.054);
}
}
.heti pre code {
margin: 0;
padding: 0;
border: 0;
border-radius: 0;
background-color: transparent;
color: inherit;
}
.heti:not(:lang(zh)):not(:lang(ja)):not(:lang(kr)), .heti:not(:lang(zh)) {
letter-spacing: 0;
}
@ -294,12 +242,12 @@
.heti h6 {
position: relative;
margin: 0;
margin-block-start: 24px;
margin-block-end: 12px;
margin-block-start: 16px;
margin-block-end: 8px;
font-weight: 600;
}
.heti h1 {
margin-block-end: 24px;
margin-block-end: 16px;
font-size: 32px;
line-height: 48px;
}
@ -340,7 +288,7 @@
.heti h3 + h4,
.heti h4 + h5,
.heti h5 + h6 {
margin-block-start: 12px;
margin-block-start: 8px;
}
.heti ul,
.heti ol,
@ -359,30 +307,11 @@
margin-block-start: 0;
margin-block-end: 0;
}
.heti ul {
list-style-type: disc;
}
.heti ol {
list-style-type: decimal;
}
.heti ul ul,
.heti ol ul {
list-style-type: circle;
}
.heti ul ul ul,
.heti ul ol ul,
.heti ol ul ul,
.heti ol ol ul {
list-style-type: square;
}
.heti li {
list-style-type: unset;
}
.heti table {
box-sizing: border-box;
table-layout: fixed;
margin-block-start: 12px;
margin-block-end: 24px;
margin-block-start: 8px;
margin-block-end: 16px;
margin-inline-start: auto;
margin-inline-end: auto;
border-collapse: collapse;
@ -401,8 +330,8 @@
}
.heti th,
.heti td {
padding-block-start: 6px;
padding-block-end: 6px;
padding-block-start: 4px;
padding-block-end: 4px;
padding-inline-start: 8px;
padding-inline-end: 8px;
border-width: 1px;
@ -444,12 +373,6 @@
.heti strong {
font-weight: 600;
}
.heti code {
margin-inline-start: 0.25em;
margin-inline-end: 0.25em;
font-family: "SFMono-Regular", consolas, "Liberation Mono", menlo, courier, monospace, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-size: 0.875em;
}
.heti dfn {
font-weight: 600;
}
@ -493,7 +416,7 @@
}
}
.heti q {
quotes: "「" "」" "『" "』";
quotes: "“" "”" "‘" "’";
}
.heti q:not(:lang(zh)):not(:lang(ja)):not(:lang(kr)), .heti q:not(:lang(zh)) {
quotes: initial;
@ -631,8 +554,8 @@
column-gap: 2em;
}
.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 {
margin-block-start: 6px;
margin-block-end: 12px;
margin-block-start: 4px;
margin-block-end: 8px;
text-indent: 2em;
}
.heti--columns-1 {
@ -689,7 +612,7 @@
text-align: start;
}
.heti--vertical q {
quotes: "「" "」" "『" "』";
quotes: "『" "』" "「" "」";
}
.heti--ancient, .heti--poetry {
font-family: "Times New Roman", times, "Heti Song", serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
@ -772,8 +695,8 @@
text-emphasis: none;
}
.heti--annotation .heti-meta {
margin-block-start: 12px;
margin-block-end: 24px;
margin-block-start: 8px;
margin-block-end: 16px;
}
.heti .heti-meta {
display: block;
@ -835,7 +758,7 @@
}
}
.heti .heti-fn ol {
margin-block-start: 12px;
margin-block-start: 8px;
margin-block-end: 0;
}
.heti .heti-fn li:target {

View file

@ -622,7 +622,7 @@
$$article.className = ['article', 'heti', e.target.value].join(' ')
})
const heti = new Heti('.article')
const heti = new Heti(document, '.article')
heti.autoSpacing()
</script>
</body>

138
js/heti-addon.js Normal file
View file

@ -0,0 +1,138 @@
/**
* Heti add-on v 0.1.0
* Add right spacing between CJK & ANS characters
*/
import Finder from 'heti-findandreplacedomtext'
const hasOwn = {}.hasOwnProperty
const HETI_NON_CONTIGUOUS_ELEMENTS = Object.assign({}, Finder.NON_CONTIGUOUS_PROSE_ELEMENTS, {
ins: 1, del: 1, s: 1, a: 1,
})
const HETI_SKIPPED_ELEMENTS = Object.assign({}, Finder.NON_PROSE_ELEMENTS, {
pre: 1, code: 1, sup: 1, sub: 1, 'heti-spacing': 1, 'heti-close': 1,
})
const HETI_SKIPPED_CLASS = 'heti-skip'
// 部分正则表达式修改自 pangu.js https://github.com/vinta/pangu.js
const CJK = '\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30fa\u30fc-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff'
const A = 'A-Za-z\u0080-\u00ff\u0370-\u03ff'
const N = '0-9'
const S = '`~!@#\\$%\\^&\\*\\(\\)-_=\\+\\[\\]{}\\\\\\|;:\'",<.>\\/\\?'
const ANS = `${A}${N}${S}`
const REG_CJK_FULL = `(?<=[${CJK}])( *[${ANS}]+(?: +[${ANS}]+)* *)(?=[${CJK}])`
const REG_CJK_START = `([${ANS}]+(?: +[${ANS}]+)* *)(?=[${CJK}])`
const REG_CJK_END = `(?<=[${CJK}])( *[${ANS}]+(?: +[${ANS}]+)*)`
const REG_CJK_FULL_WITHOUT_LOOKBEHIND = `(?:[${CJK}])( *[${ANS}]+(?: +[${ANS}]+)* *)(?=[${CJK}])`
const REG_CJK_END_WITHOUT_LOOKBEHIND = `(?:[${CJK}])( *[${ANS}]+(?: +[${ANS}]+)*)`
const REG_BD_STOP = `。.,、:;!‼?⁇`
const REG_BD_SEP = `·・‧`
const REG_BD_OPEN = `「『(《〈【〖〔[{`
const REG_BD_CLOSE = `」』)》〉】〗〕]}`
const REG_BD_START = `${REG_BD_OPEN}${REG_BD_CLOSE}`
const REG_BD_END = `${REG_BD_STOP}${REG_BD_OPEN}${REG_BD_CLOSE}`
const REG_BD_HALF_OPEN = `“‘`
const REG_BD_HALF_CLOSE = `”’`
const REG_BD_HALF_START = `${REG_BD_HALF_OPEN}${REG_BD_HALF_CLOSE}`
class Heti {
constructor (rootDocument, rootSelector) {
let supportLookBehind = true
try {
new RegExp(`(?<=\d)\d`, 'g').test('')
} catch (err) {
console.info(err.name, '该浏览器尚未实现 RegExp positive lookbehind')
supportLookBehind = false
}
this.rootDocument = rootDocument
this.rootSelector = rootSelector || '.heti'
this.REG_FULL = new RegExp(supportLookBehind ? REG_CJK_FULL : REG_CJK_FULL_WITHOUT_LOOKBEHIND, 'g')
this.REG_START = new RegExp(REG_CJK_START, 'g')
this.REG_END = new RegExp(supportLookBehind ? REG_CJK_END : REG_CJK_END_WITHOUT_LOOKBEHIND, 'g')
this.offsetWidth = supportLookBehind ? 0 : 1
this.funcForceContext = function forceContext (el) {
return hasOwn.call(HETI_NON_CONTIGUOUS_ELEMENTS, el.nodeName.toLowerCase())
}
this.funcFilterElements = function filterElements (el) {
return (
!(el.classList && el.classList.contains(HETI_SKIPPED_CLASS)) &&
!hasOwn.call(HETI_SKIPPED_ELEMENTS, el.nodeName.toLowerCase())
)
}
}
spacingElements (elmList) {
for (let $$root of elmList) {
this.spacingElement($$root)
}
}
spacingElement ($$elm) {
const commonConfig = {
forceContext: this.funcForceContext,
filterElements: this.funcFilterElements,
}
const getWrapper = (elementName, classList, text) => {
const $$r = this.rootDocument.createElement(elementName)
$$r.className = classList
$$r.textContent = text.trim()
return $$r
}
Finder($$elm, Object.assign({}, commonConfig, {
find: this.REG_FULL,
replace: portion => getWrapper('heti-spacing', 'heti-spacing-start heti-spacing-end', portion.text),
offset: this.offsetWidth,
}))
Finder($$elm, Object.assign({}, commonConfig, {
find: this.REG_START,
replace: portion => getWrapper('heti-spacing', 'heti-spacing-start', portion.text),
}))
Finder($$elm, Object.assign({}, commonConfig, {
find: this.REG_END,
replace: portion => getWrapper('heti-spacing', 'heti-spacing-end', portion.text),
offset: this.offsetWidth,
}))
Finder($$elm, Object.assign({}, commonConfig, {
find: new RegExp(`([${REG_BD_STOP}])(?=[${REG_BD_START}])|([${REG_BD_OPEN}])(?=[${REG_BD_OPEN}])|([${REG_BD_CLOSE}])(?=[${REG_BD_END}])`,'g'),
replace: portion => getWrapper('heti-adjacent', 'heti-adjacent-half', portion.text),
offset: this.offsetWidth,
}))
Finder($$elm, Object.assign({}, commonConfig, {
find: new RegExp(`([${REG_BD_SEP}])(?=[${REG_BD_OPEN}])|([${REG_BD_CLOSE}])(?=[${REG_BD_SEP}])`,'g'),
replace: portion => getWrapper('heti-adjacent', 'heti-adjacent-quarter', portion.text),
offset: this.offsetWidth,
}))
// 使用弯引号的情况下,在停顿符号接弯引号(如「。“」)或弯引号接全角开引号(如“《」)时,间距缩进调整到四分之一
Finder($$elm, Object.assign({}, commonConfig, {
find: new RegExp(`([${REG_BD_STOP}])(?=[${REG_BD_HALF_START}])|([${REG_BD_HALF_OPEN}])(?=[${REG_BD_OPEN}])`,'g'),
replace: portion => getWrapper('heti-adjacent', 'heti-adjacent-quarter', portion.text),
offset: this.offsetWidth,
}))
}
autoSpacing () {
const callback = () => {
const $$rootList = this.rootDocument.querySelectorAll(this.rootSelector)
for (let $$root of $$rootList) {
this.spacingElement($$root)
}
}
if (this.rootDocument == document) {
if (this.rootDocument.readyState === 'complete') setTimeout(callback)
else this.rootDocument.addEventListener('DOMContentLoaded', callback)
} else {
callback()
}
}
}
export default Heti

72
lib/_base.scss Normal file
View file

@ -0,0 +1,72 @@
// Author: Sivan [sun.sivan@gmail.com]
// Description: base reset and entry styles.
@import "variables";
@mixin hetiBase {
// 清容器浮动
@include clear-float();
// 清容器内首尾元素外边距
&,
section,
td {
> *:first-child {
margin-block-start: 0 !important;
}
> *:last-child {
margin-block-end: 0 !important;
}
}
figure {
display: block;
text-align: center;
> img {
display: block;
margin-inline-start: auto;
margin-inline-end: auto;
}
}
hr {
width: 30%;
height: 1px;
margin-block-start: $std-block-unit * 2;
margin-block-end: $std-block-unit * 2 - 1px;
margin-inline-start: auto;
margin-inline-end: auto;
border: 0;
background-color: hsl(0, 0%, 80%);
@include darkmode-style {
background-color: hsl(0, 0%, 25%);
}
}
p {
margin-block-start: $std-block-unit * 0.5;
margin-block-end: $std-block-unit;
text-align: justify;
@include non-cjk-block {
text-align: start;
}
}
// 非中文时不加间距
letter-spacing: $letter-spacing-medium;
@include non-cjk-block {
letter-spacing: $letter-spacing-normal;
}
a,
abbr,
code,
heti-spacing,
[lang="en-US"] {
// There should be no leeter-spacing between English characters.
letter-spacing: normal;
}
}

5
lib/_font.scss Normal file
View file

@ -0,0 +1,5 @@
// Author: Sivan [sun.sivan@gmail.com]
// Description: define font-face.
@import "fonts/hei";
@import "fonts/song";
@import "fonts/kai";

73
lib/_heading.scss Normal file
View file

@ -0,0 +1,73 @@
// Author: Sivan [sun.sivan@gmail.com]
// Description: heading styles.
@import "variables";
@mixin hetiHeading {
h1,
h2,
h3,
h4,
h5,
h6 {
position: relative;
// 顶边距默认为一行间距且不因边距重叠原因减半
// 底边距考虑到亲密性默认为半行间距
margin: 0;
margin-block-start: $std-block-unit;
margin-block-end: $std-block-unit * 0.5;
font-weight: $font-weight-bold;
}
h1 {
margin-block-end: $std-block-unit;
font-size: $font-size-h1;
line-height: $line-height-size-h1;
}
h2 {
font-size: $font-size-h2;
line-height: $line-height-size-h2;
}
h3 {
font-size: $font-size-h3;
line-height: $line-height-size-h3;
}
h4 {
font-size: $font-size-h4;
line-height: $line-height-size-h4;
}
h5 {
font-size: $font-size-h5;
line-height: $line-height-size-h5;
}
h6 {
font-size: $font-size-h6;
line-height: $line-height-size-h6;
}
h1,
h2,
h3 {
// 中文大标题增加微小文字间距
letter-spacing: 0.05em;
// 非中文时不加间距
@include non-cjk-block {
letter-spacing: 0;
}
}
// 压缩两个标题之间的间距
h1 + h2,
h2 + h3,
h3 + h4,
h4 + h5,
h5 + h6 {
margin-block-start: $std-block-unit * 0.5;
}
}

189
lib/_inline.scss Normal file
View file

@ -0,0 +1,189 @@
// Author: Sivan [sun.sivan@gmail.com]
// Description: inline element styles.
@import "variables";
@mixin hetiInline {
a {
text-decoration: none;
&:hover {
padding-block-end: 1px;
border-block-end: 1px solid currentColor;
text-decoration: none;
}
}
abbr[title] {
padding-block-end: 1px;
border-block-end: 1px dotted;
text-decoration: none;
cursor: help;
}
b,
strong {
font-weight: $font-weight-bold;
}
dfn {
font-weight: $font-weight-bold;
// 非中文时不加粗
@include non-cjk-block {
font-weight: $font-weight-normal;
}
}
em {
font-weight: $font-weight-bold;
}
// 标题单行时居中多行时居左
figcaption {
display: inline-block;
vertical-align: top;
font-size: $font-size-small;
text-align: start;
}
// 显式斜体标签予以保留
i {
font-style: italic;
}
ins,
u {
padding-block-end: 1px;
border-block-end: 1px solid;
text-decoration: none;
}
mark {
padding-block-start: 2px;
padding-block-end: 2px;
padding-inline-start: 1px;
padding-inline-end: 1px;
margin-inline-start: 1px;
margin-inline-end: 1px;
background-color: hsla(58, 100%, 50%, 0.88);
color: inherit;
@include darkmode-style {
background-color: hsla(58, 100%, 15%, 0.88);
}
}
// 设置引用文本为中文引号
// 默认无论简繁都采用台湾规范修改只需重新定义变量 `$chinese-quote-set` 即可
q {
quotes: map-get(map-get($chinese-quote-presets, $chinese-quote-set), "horizontal");
@include non-cjk-block {
quotes: initial;
quotes: auto;
}
}
rt {
font-size: 0.875em;
font-weight: $font-weight-normal;
}
// 完美 <small> 字号 by Sivan
/// 12px * 0.875 => 11px
/// 14px * 0.875 => 12px
/// 16px * 0.875 => 14px
/// 18px * 0.875 => 16px
/// 20px * 0.875 => 18px
small {
font-size: 0.875em;
}
strong {
font-weight: $font-weight-bold;
}
sub,
sup {
position: relative;
margin-inline-start: 0.25em;
margin-inline-end: 0.25em;
font-size: 0.75em;
font-family: $font-family-hei;
font-style: normal;
line-height: 1;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
&:target,
a:target {
background-color: hsl(210, 100%, 93%);
@include darkmode-style {
background-color: hsl(210, 40%, 38%);
}
}
}
summary {
padding-inline-start: 1em;
outline: 0;
cursor: pointer;
}
summary::-webkit-details-marker {
width: 0.6em;
margin-inline-end: 0.4em;
}
u[title] {
cursor: help;
border-block-end-width: 3px;
border-block-end-style: double;
border-block-end-color: hsla(0, 0%, 0%, 0.54);
@include darkmode-style {
border-block-end-color: hsla(0, 0%, 100%, 0.54);
}
}
// 默认禁用中文斜体 https://www.zhihu.com/question/20120243
address,
cite,
dfn,
dt,
em {
font-style: normal;
// 非中文时显示斜体
@include non-cjk-block {
font-style: italic;
}
}
// 为带划线的元素添加间距以防止视觉上混为一个元素
// : 如果设成为两个相连元素添加间距会有一个问题
// 如果结构是 `<u>倚天</u><u>屠龙</u>`屠龙前面仍然会有边距
// 此处跟预期不一致应该只在两个同名元素紧邻时增加边距`<u>倚天剑</u><u>屠龙刀</u>`
//@each $tag in (abbr[title], del, ins, s, u) {
// #{$tag} + #{$tag} {
// margin-inline-start: 0.125em;
// }
//}
// 因此采用下面的形式为所有加划线的元素增加缝隙
abbr[title],
del,
ins,
s,
u {
margin-inline-start: 1px;
margin-inline-end: 1px;
}
}

54
lib/_list.scss Normal file
View file

@ -0,0 +1,54 @@
// Author: Sivan [sun.sivan@gmail.com]
// Description: list styles.
@import "variables";
@mixin hetiList {
// 标准化间距
ul,
ol,
dl {
margin-block-start: $line-height-size-normal * 0.5;
margin-block-end: $line-height-size-normal;
}
ul,
ol {
padding-inline-start: $text-indent-size;
ul,
ol {
margin-block-start: 0;
margin-block-end: 0;
}
}
// 兼容性处理
/// 重置部分 CSS Reset ul, ol { list-style: none; } 造成的样式污染
/// 如果搭配 normalize.css 使用则不存在这些样式污染
@if $_css-reset-scheme == "reset" {
ul {
list-style-type: disc;
}
ol {
list-style-type: decimal;
}
ul ul,
ol ul {
list-style-type: circle;
}
ul ul ul,
ul ol ul,
ol ul ul,
ol ol ul {
list-style-type: square;
}
// 重置不知道哪里散播出来的垃圾代码 ul, li { list-style: none; }
li {
list-style-type: unset;
}
}
}

46
lib/_table.scss Normal file
View file

@ -0,0 +1,46 @@
// Author: Sivan [sun.sivan@gmail.com]
// Description: table styles.
@import "variables";
@mixin hetiTable {
table {
box-sizing: border-box;
table-layout: fixed;
margin-block-start: $std-block-unit * 0.5;
margin-block-end: $std-block-unit;
margin-inline-start: auto;
margin-inline-end: auto;
border-collapse: collapse;
border-width: 1px;
border-style: solid;
border-color: hsl(0, 0%, 80%);
word-break: break-word;
@include darkmode-style {
border-color: hsl(0, 0%, 25%);
}
}
th,
td {
padding-block-start: $std-block-unit * 0.25;
padding-block-end: $std-block-unit * 0.25;
padding-inline-start: $std-inline-unit * 0.5;
padding-inline-end: $std-inline-unit * 0.5;
border-width: 1px;
border-style: solid;
border-color: hsl(0, 0%, 80%);
@include darkmode-style {
border-color: hsl(0, 0%, 25%);
}
}
caption {
caption-side: bottom;
margin-block-start: 2px;
margin-block-end: -4px;
font-size: $font-size-small;
line-height: $line-height-size-small;
}
}

178
lib/_variables.scss Normal file
View file

@ -0,0 +1,178 @@
// Author: Sivan [sun.sivan@gmail.com]
// Description: define variables, alias etc.
// 定义赫蹏根 class
$root-selector: '.heti' !default;
$darkmode: true !default; // true | false | 'manual'
$manualmode-auto-selector: '[data-darkmode="auto"] &' !default;
$manualmode-dark-selector: '[data-darkmode="dark"] &' !default;
// 字体 Fonts
// 字体栈 Font Stacks
$_font-stack-sans: "Helvetica Neue", helvetica, arial !default;
$_font-stack-serif: "Times New Roman", times !default;
$_font-stack-mono: "SFMono-Regular", consolas, "Liberation Mono", menlo, courier !default;
$_font-stack-symbol: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !default;
// 字体族 Font Families
$font-family-hei: $_font-stack-sans, "Heti Hei", sans-serif, $_font-stack-symbol !default;
$font-family-song: $_font-stack-serif, "Heti Song", serif, $_font-stack-symbol !default;
$font-family-kai: $_font-stack-serif, "Heti Kai", serif, $_font-stack-symbol !default;
$font-family-hei-black: $_font-stack-sans, "Heti Hei Black", sans-serif, $_font-stack-symbol !default;
$font-family-song-black: $_font-stack-serif, "Heti Song Black", serif, $_font-stack-symbol !default;
$font-family-kai-black: $_font-stack-serif, "Heti Kai Black", serif, $_font-stack-symbol !default;
$font-family-mono: $_font-stack-mono, monospace, $_font-stack-symbol !default;
// 字重 Font Weights
$font-weight-bolder: 800 !default;
$font-weight-bold: 600 !default;
$font-weight-normal: 400 !default;
$font-weight-lighter: 200 !default;
// 字号 Font Sizes
$font-size-normal: 16px !default;
$font-size-x-large: 20px !default;
$font-size-large: 18px !default;
$font-size-small: 14px !default;
$font-size-x-small: 12px !default;
$font-size-h1: 32px !default;
$font-size-h2: 24px !default;
$font-size-h3: 20px !default;
$font-size-h4: 18px !default;
$font-size-h5: 16px !default;
$font-size-h6: 14px !default;
// Lines
// 行宽
$line-length: 42em !default;
// 行高 Line Heights
$line-height-normal: 1.5 !default;
//$line-height-expanded: 1.875 !default;
$line-height-expanded-ultra: 2.25 !default;
//$line-height-condensed: 1.25 !default;
//$line-height-condensed-ultra: 1 !default;
// 字符间距
$letter-spacing-normal: 0 !default;
$letter-spacing-small: 0.01em !default;
$letter-spacing-medium: 0.02em !default;
$letter-spacing-large: 0.05em !default;
$line-height-size-normal: $font-size-normal * $line-height-normal !default;
$line-height-size-large: $line-height-size-normal !default;
$line-height-size-x-large: $font-size-x-large * $line-height-normal !default;
$line-height-size-small: $line-height-size-normal !default;
$line-height-size-x-small: 18px !default;
$line-height-size-h1: 48px !default;
$line-height-size-h2: 36px !default;
$line-height-size-h3: 36px !default;
$line-height-size-h4: 24px !default;
$line-height-size-h5: 24px !default;
$line-height-size-h6: 24px !default;
// 段落 Paragraphs
// 标准网格单位变量 Standard Length
// 垂直方向标准单位 = 标准行高
// 水平方向标准单位 = 标准字号
$std-block-unit: 16px;
$std-inline-unit: $font-size-normal !default;
// 示例缩进单位 = 二倍文字宽度
$text-indent-length: 2em !default;
$text-indent-size: $font-size-normal * 2 !default;
// 中文引号 Chinese Quote Set
// `cn`中华人民共和国国家标准GB/T 15834-2011标点符号用法 http://www.moe.gov.cn/ewebeditor/uploadfile/2015/01/13/20150113091548267.pdf
// `tw`中国台湾地区标准重訂標點符號手冊 https://language.moe.gov.tw/001/Upload/FILES/SITE_CONTENT/M0001/HAU/h6.htm
// `common`部分中文社区如知乎在简体中文里亦采用与中国台湾地区标准一致的规范
// 垂直时浏览器会自动旋转无需定义为垂直方向的字符
$chinese-quote-presets: (
"cn": (
"horizontal": "" "" "" "",
"vertical": "" "" "" ""
),
"tw": (
"horizontal": "" "" "" "",
"vertical": "" "" "" ""
),
"common": (
"horizontal": "" "" "" "",
"vertical": "" "" "" ""
)
) !default;
$chinese-quote-set: "cn" !default;
// Columns
// 分栏
$column-count-list: (1, 2, 3, 4) !default;
$column-width-list: (16em, 20em, 24em, 28em, 32em, 36em, 40em, 44em, 48em) !default;
// 开发用配置项 Develop Configs
/// 预设重置方案
/// `reset`假定 Eric Meyer CSS Reset 或其它流行的 Reset
/// `normalize`指定为 normalize.css
$_css-reset-scheme: "normalize";
// 混合 Mix-ins
// Mix-in: Clear float
@mixin clear-float {
&::before,
&::after {
content: "";
display: table;
}
&::after {
clear: both;
}
}
// Mix-in: Include Non-cjk styles
@mixin non-cjk-block {
&:not(:lang(zh)):not(:lang(ja)):not(:lang(kr)),
&:not(:lang(zh)) {
@content;
}
}
// Mix-in: Hang Punctuation Mark
@mixin hang {
position: absolute;
line-height: inherit;
text-indent: 0;
}
@mixin darkmode-style($darkmode: $darkmode, $dark-selector: $manualmode-dark-selector, $auto-selector: $manualmode-auto-selector) {
// 'manual' darkmode should provide darkmode selector and auto-darkmode selector
@if $darkmode == 'manual' {
#{$dark-selector} {
@content;
}
@media (prefers-color-scheme: dark) {
#{$auto-selector} {
@content;
}
}
} @else if $darkmode {
@media (prefers-color-scheme: dark) {
@content;
}
}
}
// 函数 Functions
// Function: batch prefix/suffix list-item
// batch-fix-list((2, 3), '#{$root-selector}--columns-') => (.heti--columns-2, .heti--columns-3)
@function batch-fix-list($list, $prefix: '', $suffix: '') {
$_list: () !default;
@each $item in $list {
$_list: append($_list, #{$prefix}#{$item}#{$suffix}, comma);
}
@return $_list;
}

232
lib/fonts/_hei.scss Normal file
View file

@ -0,0 +1,232 @@
// Author: Sivan [sun.sivan@gmail.com], Pan RZ [c141028@gmail.com]
// Description: define font-face Heti Hei.
// 标准 Regular
@font-face {
font-family: "Heti Hei";
src:
"Heti Hei SC",
"Heti Hei TC",
"Heti Hei JP",
"Heti Hei KR";
}
@font-face {
font-family: "Heti Hei SC";
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");
}
@font-face {
font-family: "Heti Hei TC";
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");
}
@font-face {
font-family: "Heti Hei JP";
src:
local("Hiragino Sans GB W3"),
local("Source Han Sans JP Regular"),
local("Noto Sans CJK JP Regular"),
local("Droid Sans Fallback");
}
@font-face {
font-family: "Heti Hei KR";
src:
local("Source Han Sans KR Regular"),
local("Noto Sans CJK KR Regular"),
local("Droid Sans Fallback");
}
// 细体 Light
@font-face {
font-family: "Heti Hei";
font-weight: 200;
src:
"Heti Hei SC Light",
"Heti Hei TC Light",
"Heti Hei JP Light",
"Heti Hei KR Light";
}
@font-face {
font-family: "Heti Hei SC Light";
font-weight: 200;
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");
}
@font-face {
font-family: "Heti Hei TC Light";
font-weight: 200;
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");
}
@font-face {
font-family: "Heti Hei JP Light";
font-weight: 200;
src:
local("Source Han Sans JP Light"),
local("Noto Sans CJK JP Light");
}
@font-face {
font-family: "Heti Hei KR Light";
font-weight: 200;
src:
local("Source Han Sans KR Light"),
local("Noto Sans CJK KR Light");
}
@font-face {
font-family: "Heti Hei SC Light Fallback";
font-weight: 200;
src:
local("Microsoft YaHei"),
local("Droid Sans Fallback");
}
// 粗体 Bold
@font-face {
font-family: "Heti Hei";
font-weight: 600;
src:
"Heti Hei SC Bold",
"Heti Hei TC Bold",
"Heti Hei JP Bold",
"Heti Hei KR Bold";
}
@font-face {
font-family: "Heti Hei SC Bold";
font-weight: 600;
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");
}
@font-face {
font-family: "Heti Hei TC Bold";
font-weight: 600;
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");
}
@font-face {
font-family: "Heti Hei JP Bold";
font-weight: 600;
src:
local("Hiragino Sans GB W6"),
local("Source Han Sans JP Bold"),
local("Noto Sans CJK JP Bold");
}
@font-face {
font-family: "Heti Hei KR Bold";
font-weight: 600;
src:
local("Source Han Sans KR Bold"),
local("Noto Sans CJK KR Bold");
}
@font-face {
font-family: "Heti Hei SC Bold Fallback";
font-weight: 600;
src:
local("Microsoft YaHei"),
local("Droid Sans Fallback");
}
// 黑体 Black
@font-face {
font-family: "Heti Hei Black";
font-weight: 800;
src:
"Heti Hei SC Black",
"Heti Hei TC Black",
"Heti Hei JP Black",
"Heti Hei KR Black";
}
@font-face {
font-family: "Heti Hei SC Black";
font-weight: 800;
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");
}
@font-face {
font-family: "Heti Hei TC Black";
font-weight: 800;
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");
}
@font-face {
font-family: "Heti Hei JP Black";
font-weight: 800;
src:
local("Hiragino Sans GB W6"),
local("Source Han Sans JP Heavy"),
local("Noto Sans CJK JP Heavy");
}
@font-face {
font-family: "Heti Hei KR Black";
font-weight: 800;
src:
local("Source Han Sans KR Heavy"),
local("Noto Sans CJK KR Heavy");
}
@font-face {
font-family: "Heti Hei SC Black Fallback";
font-weight: 800;
src:
local("Microsoft YaHei"),
local("Droid Sans Fallback");
}

42
lib/fonts/_kai.scss Normal file
View file

@ -0,0 +1,42 @@
// Author: Sivan [sun.sivan@gmail.com], Pan RZ [c141028@gmail.com]
// Description: define font-face Heti Kai.
// 标准 Regular
@font-face {
font-family: "Heti Kai";
src:
local("Kaiti SC Regular"),
local("Kaiti TC Regular"),
local("STKaiti"),
local("Kaiti"),
local("BiauKai");
}
// 粗体 Bold
@font-face {
font-family: "Heti Kai";
font-weight: 600;
src:
local("Kaiti SC Bold"),
local("Kaiti TC Bold");
}
@font-face {
font-family: "Heti Kai Bold Fallback";
font-weight: 600;
src:
local("STKaiti"),
local("Kaiti")
local("BiauKai");
}
// 黑体 Black
@font-face {
font-family: "Heti Kai Black";
font-weight: 800;
src:
local("Kaiti SC Black"),
local("Kaiti TC Black"),
local("STKaiti"),
local("Kaiti");
}

54
lib/fonts/_song.scss Normal file
View file

@ -0,0 +1,54 @@
// Author: Sivan [sun.sivan@gmail.com], Pan RZ [c141028@gmail.com]
// Description: define font-face Heti Song.
// 标准 Regular
@font-face {
font-family: "Heti Song";
src:
local("Songti SC Regular"),
local("Songti TC Regular"),
local("SimSun");
}
// 细体 Light
@font-face {
font-family: "Heti Song";
font-weight: 200;
src:
local("Songti SC Light"),
local("Songti TC Light"),
"Heti Song Light Fallback";
}
@font-face {
font-family: "Heti Song Light Fallback";
font-weight: 200;
src: local("SimSun");
}
// 粗体 Bold
@font-face {
font-family: "Heti Song";
font-weight: 600;
src:
local("Songti SC Bold"),
local("Songti TC Bold"),
"Heti Song Bold Fallback";
}
@font-face {
font-family: "Heti Song Bold Fallback";
font-weight: 600;
src: local("SimSun");
}
// 黑体 Black
// 只支持简体
@font-face {
font-family: "Heti Song Black";
font-weight: 800;
src:
local("Songti SC Black"),
// local("Songti TC Black"),
local("SimSun");
}

35
lib/helpers/_add-on.scss Normal file
View file

@ -0,0 +1,35 @@
// Author: Sivan [sun.sivan@gmail.com]
// Description: define add-ons.
@import "../variables";
@mixin hetiAddOns {
// 用于中西文混排增加边距
heti-spacing {
display: inline;
& + sup,
& + sub {
margin-inline-start: 0;
}
}
.heti-spacing-start {
margin-inline-end: 0.25em;
}
.heti-spacing-end {
margin-inline-start: 0.25em;
}
heti-adjacent {
display: inline;
}
.heti-adjacent-half {
margin-inline-end: -0.5em;
}
.heti-adjacent-quarter {
margin-inline-end: -0.25em;
}
}

98
lib/helpers/_block.scss Normal file
View file

@ -0,0 +1,98 @@
// Author: Sivan [sun.sivan@gmail.com]
// Description: define block helper classes.
@import "../variables";
@mixin hetiBlockHelperClasses {
// 段落相关
// 元信息无缩进
#{$root-selector}-meta {
display: block;
text-indent: 0;
}
// 诗节无缩进居中显示
#{$root-selector}-verse {
text-align: center;
text-indent: 0;
}
// 定义扩展字号
#{$root-selector} {
&-large {
font-size: $font-size-large;
line-height: $line-height-size-large;
}
&-x-large {
font-size: $font-size-x-large;
line-height: $line-height-size-x-large;
letter-spacing: 0.05em;
}
&-small {
font-size: $font-size-small;
line-height: $line-height-size-small;
}
&-x-small {
font-size: $font-size-x-small;
line-height: $line-height-size-x-small;
}
}
// 列表相关
// 定义拉丁字母的有序列表
#{$root-selector}-list-latin {
list-style-type: upper-latin;
ol {
list-style-type: lower-roman;
ol {
list-style-type: lower-latin;
}
}
}
// 定义中文序号的有序列表
#{$root-selector}-list-han {
list-style-type: cjk-ideographic;
ol {
list-style-type: decimal;
ol {
list-style-type: decimal-leading-zero;
}
}
}
// 页脚
#{$root-selector}-fn {
margin-block-start: 59px;
border-block-start: 1px solid;
border-block-start-color: hsl(0, 0%, 80%);
font-size: $font-size-small;
font-family: $font-family-hei;
line-height: $line-height-size-normal;
@include darkmode-style {
border-block-start-color: hsl(0, 0%, 25%);
}
ol {
margin-block-start: $std-block-unit * 0.5;
margin-block-end: 0;
}
li {
&:target {
background-color: hsl(210, 100%, 93%);
@include darkmode-style {
background-color: hsl(210, 40%, 38%);
}
}
}
}
}

43
lib/helpers/_inline.scss Normal file
View file

@ -0,0 +1,43 @@
// Author: Sivan [sun.sivan@gmail.com]
// Description: define inline helper classes.
@import "../variables";
@mixin hetiInlineHelperClasses {
// 标点悬挂 Punctuation mark hanging
// @todo: 用于标点悬挂用的样式
#{$root-selector}-hang {
@include hang();
}
// 强烈着重号 Emphasis Mark
#{$root-selector}-em {
-webkit-text-emphasis: filled circle;
-webkit-text-emphasis-position: under;
text-emphasis: filled circle;
text-emphasis-position: under right;
@include non-cjk-block {
-webkit-text-emphasis: none;
text-emphasis: none;
}
}
// 内联 Ruby
// 在非行间注排版中使用 ruby 标签且不额外占据空间
// 使用此样式需按字拆分 <ruby class="heti-ruby heti-ruby--inline"><rb></rb><rp>(</rp><rt></rt><rp>)</rp></ruby>
// https://stackoverflow.com/questions/38680695/adjust-the-vertical-positioning-of-ruby-text/38877801#38877801
#{$root-selector}-ruby {
&--inline {
display: inline-flex;
flex-direction: column-reverse;
height: 1.5em;
rt {
display: inline;
margin-bottom: -0.25em;
line-height: 1;
text-align: center;
}
}
}
}

63
lib/heti.scss Normal file
View file

@ -0,0 +1,63 @@
/*!
* Project: Heti
* URL: https://github.com/sivan/heti
* Author: Sivan [sun.sivan@gmail.com]
*/
@import "font";
@import "variables";
@import "base";
@import "heading";
@import "list";
@import "table";
@import "inline";
@import "modifiers/font-stack";
@import "modifiers/writing-mode";
@import "modifiers/column";
@import "modifiers/ancient";
@import "modifiers/annotation";
@import "helpers/block";
@import "helpers/inline";
@import "helpers/add-on";
#{$root-selector} {
// 中文每行展示文字CPL建议在 30~50 之间默认 42
max-width: $line-length;
// 默认字体大小为 16px行高 1.5
font-size: $font-size-normal;
font-weight: $font-weight-normal;
-webkit-font-smoothing: subpixel-antialiased;
// 针对混合英文段落采取按词折行长单词通过连词符段行
// https://justmarkup.com/articles/2015-07-31-dealing-with-long-words-in-css/
overflow-wrap: break-word;
word-wrap: break-word;
hyphens: auto;
// 自动在中西文间加 1/4 空格暂无浏览器支持
//text-spacing: ideograph-alpha;
// 模块引用顺序
// 1. 引入各模块
// .heti h1, .heti p, .heti ul
@include hetiBase();
@include hetiHeading();
@include hetiList();
@include hetiTable();
@include hetiInline();
// 2. 定义所有修饰器需与 .heti 组合使用
// .heti--sans h1, .heti--vertical h1 etc.
@include hetiFontModifier();
@include hetiColumnModifier();
@include hetiWritingModeModifier();
@include hetiAncientModifier();
@include hetiAnnotationModifier();
// 3. 定义工具类样式仅在 .heti 内部使用
// .heti .heti-verse, .heti .heti-hang
@include hetiBlockHelperClasses();
@include hetiInlineHelperClasses();
@include hetiAddOns();
}

View file

@ -0,0 +1,58 @@
// Author: Sivan [sun.sivan@gmail.com]
// Description: define ancient styles.
@import "../variables";
@mixin hetiAncientModifier {
// 定义古文古诗样式
&--ancient,
&--poetry {
// 古文古诗用宋体覆盖全局字体
font-family: $font-family-song;
// 古文标题用楷体覆盖全局字体
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: $font-family-kai-black;
font-weight: $font-weight-bolder;
text-align: center;
// 标题内元信息仅在桌面端采取悬挂处理且不占据空间影响文字居中
#{$root-selector}-meta {
font-weight: $font-weight-normal;
@media screen and (min-width: 640px) {
@include hang();
display: inline;
margin-block-start: 4px;
margin-inline-start: 8px;
}
}
}
#{$root-selector}-meta {
line-height: $line-height-size-normal;
text-align: center;
text-indent: 0;
}
}
&--ancient {
// 古文文言文首行缩进
p {
text-indent: $text-indent-length;
}
}
&--poetry {
// 诗节无缩进居中显示
p {
text-align: center;
text-indent: 0;
}
}
}

View file

@ -0,0 +1,38 @@
// Author: Sivan [sun.sivan@gmail.com]
// Description: define interlinear annotation styles.
@import "../variables";
@mixin hetiAnnotationModifier {
&--annotation {
// 首行缩进且行间距加大去除段落间距
p {
margin-block-start: 0;
margin-block-end: 0;
line-height: $line-height-expanded-ultra;
text-indent: $text-indent-length;
}
// 着重号不应影响行间距经测试最小可用行高为 1.7
em {
-webkit-text-emphasis: filled circle;
-webkit-text-emphasis-position: under;
text-emphasis: filled circle;
text-emphasis-position: under right;
font-weight: $font-weight-normal;
@include non-cjk-block {
-webkit-text-emphasis: none;
text-emphasis: none;
}
}
// 元信息保持间距
#{$root-selector}-meta {
margin-block-start: $std-block-unit * 0.5;
margin-block-end: $std-block-unit;
}
// ruby 不应影响行间距经测试最小可用行高为 2
//ruby {}
}
}

33
lib/modifiers/column.scss Normal file
View file

@ -0,0 +1,33 @@
// Author: Sivan [sun.sivan@gmail.com]
// Description: define column styles.
@import "../variables";
@mixin hetiColumnModifier {
// 定义多栏排版
$selector-list: batch-fix-list(join($column-count-list, $column-width-list), '&--columns-');
#{join($selector-list, comma)} {
// 多行时不再设总宽度限制
max-width: none;
column-gap: 2em;
// 多栏排版时减半段间距
p {
margin-block-start: $std-block-unit * 0.5 * 0.5;
margin-block-end: $std-block-unit * 0.5;
text-indent: $text-indent-length;
}
}
@each $columns in $column-count-list {
&--columns-#{$columns} {
column-count: #{$columns};
}
}
@each $column-width in $column-width-list {
&--columns-#{$column-width} {
column-width: #{$column-width};
}
}
}

View file

@ -0,0 +1,62 @@
// Author: Sivan [sun.sivan@gmail.com]
// Description: define global font stacks.
@import "../variables";
@mixin hetiFontModifier {
// 定义默认采用无衬线字体
// 定义无衬线字体为黑体
&,
&--sans {
font-family: $font-family-hei;
}
// 定义衬线字体为宋体
&--serif {
font-family: $font-family-song;
}
// 定义传统字体
// 传统字体仅供设计参考主旨为标题用楷体正文用宋体表头等客观指标描述场合用黑体
// 由于渲染机制等原因应尽量避免衬线非衬线字体混排会引起行高不一致的bug
&--classic {
// 正文使用宋体
font-family: $font-family-song;
// 标题使用楷体 800 字重
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: $font-family-kai-black;
font-weight: $font-weight-bolder;
}
// 引用使用楷体
blockquote,
cite,
q {
font-family: $font-family-kai;
}
// 说明文字表头等反应客观指标事物的位置使用黑体
figcaption,
caption,
th {
font-family: $font-family-hei;
}
}
&--hei {
font-family: $font-family-hei;
}
&--song {
font-family: $font-family-song;
}
&--kai {
font-family: $font-family-kai;
}
}

View file

@ -0,0 +1,27 @@
// Author: Sivan [sun.sivan@gmail.com]
// Description: define writing mode styles.
@import "../variables";
@mixin hetiWritingModeModifier {
// 定义垂直布局
&--vertical {
max-width: none;
max-height: $line-length;
writing-mode: vertical-rl;
letter-spacing: 0.125em;
h1,
h2,
h3,
h4,
h5,
h6 {
text-align: start;
}
// 默认无论简繁都采用台湾规范修改只需重新定义变量 `$chinese-quote-set` 即可
q {
quotes: map-get(map-get($chinese-quote-presets, $chinese-quote-set), "vertical");
}
}
}

6341
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

80
package.json Normal file
View file

@ -0,0 +1,80 @@
{
"name": "heti",
"version": "0.9.2",
"description": "赫蹏是专为中文内容展示设计的排版样式增强。它基于通行的中文排版规范而来,可以为网站的读者带来更好的文章阅读体验。",
"main": "lib/heti.scss",
"files": [
"umd",
"js",
"lib"
],
"scripts": {
"start": "sass -w --no-source-map _site/scss/:_site/",
"compile": "rollup -c -w",
"build:style": "sass --no-source-map --style=compressed lib/heti.scss:umd/heti.min.css",
"build:site": "sass --no-source-map _site/scss/:_site/",
"build:script": "rollup -c",
"build": "npm run build:style && npm run build:site && npm run build:script",
"test": "npx stylelint --config package.json 'lib/**/*.scss'"
},
"repository": {
"type": "git",
"url": "git+https://github.com/sivan/heti.git"
},
"keywords": [
"typography",
"clreq",
"css",
"sass",
"scss"
],
"author": "Sivan <sun.sivan@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/sivan/heti/issues"
},
"homepage": "https://github.com/sivan/heti#readme",
"dependencies": {
"heti-findandreplacedomtext": "^0.5.0"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^11.1.0",
"@rollup/plugin-node-resolve": "^7.1.3",
"rollup": "^1.32.1",
"rollup-plugin-terser": "^5.3.1",
"sass": "^1.57.0",
"stylelint": "^13.13.1",
"stylelint-config-recommended-scss": "^4.3.0",
"stylelint-config-standard": "^22.0.0",
"stylelint-scss": "^3.21.0"
},
"stylelint": {
"extends": [
"stylelint-config-standard",
"stylelint-config-recommended-scss"
],
"rules": {
"no-descending-specificity": null,
"at-rule-empty-line-before": null,
"block-opening-brace-space-before": "always",
"block-closing-brace-newline-after": [
"always",
{
"ignoreAtRules": [
"if",
"else"
]
}
],
"rule-empty-line-before": null,
"selector-type-no-unknown": [
true,
{
"ignore": [
"custom-elements"
]
}
]
}
}
}

33
rollup.config.js Normal file
View file

@ -0,0 +1,33 @@
import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import {terser} from 'rollup-plugin-terser';
export default {
input: 'js/heti-addon.js',
output: [
{
file: '_site/heti-addon.js',
name: 'Heti',
format: 'umd'
},
{
file: 'umd/heti-addon.min.js',
format: 'umd',
name: 'Heti',
plugins: [
terser({
compress: {
pure_funcs: ["console.info"] // 移除调试信息
},
output: {
comments: false
}
})
]
},
],
plugins: [
resolve(),
commonjs(),
]
};