hexo-theme-stellar 1.41.0 → 1.42.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/stellar-theme-dev/SKILL.md +2 -2
- package/CHANGELOG.md +60 -0
- package/CONTRIBUTING.md +2 -1
- package/README.md +50 -21
- package/README_EN.md +113 -0
- package/_config.yml +11 -6
- package/_data/icons.yml +0 -2
- package/layout/_partial/cover/index.ejs +0 -3
- package/layout/_partial/main/navbar/article_banner.ejs +30 -27
- package/layout/_partial/main/pin_slider.ejs +59 -28
- package/layout/_partial/main/post_list/latest_post_card.ejs +25 -0
- package/layout/_partial/main/post_list/post_card.ejs +16 -64
- package/layout/_partial/main/post_list/topic_card.ejs +26 -5
- package/layout/_partial/scripts/bootstrap.ejs +37 -0
- package/layout/_partial/scripts/defines.ejs +2 -1
- package/layout/_partial/scripts.ejs +9 -0
- package/layout/_partial/widgets/toc.ejs +2 -2
- package/layout/_plugins/adaptive_text.ejs +11 -0
- package/layout/_plugins/copycode.ejs +2 -2
- package/layout/_plugins/index.ejs +25 -0
- package/layout/_plugins/mermaid.ejs +1 -1
- package/layout/_plugins/scrollreveal.ejs +22 -16
- package/layout/_plugins/swiper.ejs +1 -1
- package/layout/index.ejs +10 -2
- package/layout/index_topic.ejs +4 -4
- package/package.json +3 -3
- package/scripts/filters/lib/img_lazyload.js +184 -40
- package/scripts/generators/stellar-icons.js +1 -1
- package/scripts/helpers/subtitle.js +8 -0
- package/scripts/lib/subtitle.js +24 -0
- package/scripts/tags/lib/banner.js +4 -3
- package/source/css/_common/cover-overlay.styl +111 -0
- package/source/css/_common/title.styl +17 -0
- package/source/css/_components/layout.styl +9 -13
- package/source/css/_components/list.styl +135 -64
- package/source/css/_components/main.styl +2 -2
- package/source/css/_components/pages/archives.styl +4 -1
- package/source/css/_components/pages/article-story.styl +1 -10
- package/source/css/_components/partial/article-banner.styl +61 -37
- package/source/css/_components/partial/article-tags.styl +4 -1
- package/source/css/_components/partial/bread-nav.styl +7 -14
- package/source/css/_components/partial/navbar.styl +1 -1
- package/source/css/_components/partial/post-panel.styl +76 -0
- package/source/css/_components/pin-slider.styl +38 -41
- package/source/css/_components/sidebar/footer.styl +1 -1
- package/source/css/_components/sidebar/logo.styl +2 -2
- package/source/css/_components/sidebar/search.styl +1 -1
- package/source/css/_components/sidebar/sidebar.styl +7 -7
- package/source/css/_components/tag-plugins/banner.styl +53 -11
- package/source/css/_components/tag-plugins/friends_posts.styl +5 -86
- package/source/css/_components/tag-plugins/gallery.styl +3 -0
- package/source/css/_components/tag-plugins/read/paper.styl +3 -3
- package/source/css/_components/tag-plugins/read/reel.styl +4 -4
- package/source/css/_components/tag-plugins/timeline.styl +4 -0
- package/source/css/_components/widgets/ghrepo.styl +4 -0
- package/source/css/_components/widgets/timeline.styl +2 -2
- package/source/css/_components/widgets/toc.styl +12 -6
- package/source/css/_components/widgets/widgets.styl +8 -7
- package/source/css/_custom.styl +8 -9
- package/source/css/_plugins/lazyload.styl +1 -0
- package/source/css/comments/waline.styl +24 -0
- package/source/js/color.js +325 -0
- package/source/js/main.js +2 -2
- package/source/js/plugins/adaptive-text.js +146 -0
- package/source/js/services/friends_and_posts.js +1 -1
- package/source/js/services/timeline.js +1 -1
- package/source/js/services/weibo.js +1 -1
- package/source/js/services.js +1 -1
- package/source/js/utils.js +14 -1
- package/layout/_partial/cover/post_cover.ejs +0 -37
|
@@ -1,56 +1,200 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* img_lazyload.js
|
|
2
|
+
* img_lazyload.js v2 | https://github.com/xaoxuu/hexo-theme-stellar/
|
|
3
3
|
*
|
|
4
|
+
* 懒加载强制开启:不再读取 enable 配置,no-lazy 是唯一例外。
|
|
5
|
+
* v2:改为属性感知的 <img> 标签解析,仅在标签自身范围内读取 src,
|
|
6
|
+
* 并跳过 <script>/<style>/<!-- --> 区域,避免与「去属性引号」的 HTML
|
|
7
|
+
* 压缩器(如 hexo-minify removeAttributeQuotes)组合时正则跨标签越界,
|
|
8
|
+
* 误改写页内内联脚本中的 `s.src = "..."`。
|
|
4
9
|
*/
|
|
5
10
|
|
|
6
|
-
|
|
11
|
+
"use strict";
|
|
7
12
|
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
// 1×1 透明 PNG:懒加载占位图
|
|
14
|
+
const LOADING_IMG =
|
|
15
|
+
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAAaADAAQAAAABAAAAAQAAAADa6r/EAAAAC0lEQVQIHWNgAAIAAAUAAY27m/MAAAAASUVORK5CYII=";
|
|
16
|
+
|
|
17
|
+
// 解析 <img ...> 标签内部属性,返回 [{name, value, raw, start, end}]
|
|
18
|
+
function parseAttrs(inner) {
|
|
19
|
+
const attrs = [];
|
|
20
|
+
const re = /([^\s=/>]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+)))?/g;
|
|
21
|
+
let m;
|
|
22
|
+
while ((m = re.exec(inner)) !== null) {
|
|
23
|
+
attrs.push({
|
|
24
|
+
name: m[1].toLowerCase(),
|
|
25
|
+
value: m[2] !== undefined ? m[2] : m[3] !== undefined ? m[3] : m[4] !== undefined ? m[4] : null,
|
|
26
|
+
raw: m[0],
|
|
27
|
+
start: m.index,
|
|
28
|
+
end: m.index + m[0].length,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return attrs;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 处理单个 <img> 标签:加 lazy 类并替换 src 为占位图 + data-src
|
|
35
|
+
function processImgTag(imgTag) {
|
|
36
|
+
// 快速跳过:整标签已含懒加载标记 / 原生 srcset / no-lazy(兼容压缩前后写法)
|
|
37
|
+
if (/data-src/gi.test(imgTag)) {
|
|
38
|
+
return imgTag;
|
|
39
|
+
}
|
|
40
|
+
if (/srcset=/gi.test(imgTag)) {
|
|
41
|
+
return imgTag;
|
|
42
|
+
}
|
|
43
|
+
if (/\bno-lazy\b/gi.test(imgTag)) {
|
|
44
|
+
return imgTag;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const selfClosing = /\/>$/.test(imgTag);
|
|
48
|
+
const inner = imgTag.slice(4, selfClosing ? imgTag.length - 2 : imgTag.length - 1);
|
|
49
|
+
const attrs = parseAttrs(inner);
|
|
50
|
+
const src = attrs.find((a) => a.name === "src");
|
|
51
|
+
// 无 src / 空 src / 已是占位图(data:image)时不处理
|
|
52
|
+
if (!src || src.value == null || src.value === "" || /^data:image/i.test(src.value)) {
|
|
53
|
+
return imgTag;
|
|
13
54
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
55
|
+
|
|
56
|
+
const classAttr = attrs.find((a) => a.name === "class");
|
|
57
|
+
const oldClass = classAttr && classAttr.value != null ? classAttr.value : "";
|
|
58
|
+
const newClass = (oldClass ? oldClass + " " : "") + "lazy";
|
|
59
|
+
|
|
60
|
+
let out = "<img";
|
|
61
|
+
if (!classAttr) {
|
|
62
|
+
out += ' class="' + newClass + '"';
|
|
63
|
+
}
|
|
64
|
+
let pos = 0;
|
|
65
|
+
for (const a of attrs) {
|
|
66
|
+
out += inner.slice(pos, a.start);
|
|
67
|
+
if (a.name === "src") {
|
|
68
|
+
out += 'src="' + LOADING_IMG + '" data-src="' + src.value + '"';
|
|
69
|
+
} else if (a.name === "class") {
|
|
70
|
+
out += 'class="' + newClass + '"';
|
|
71
|
+
} else {
|
|
72
|
+
out += a.raw;
|
|
18
73
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
74
|
+
pos = a.end;
|
|
75
|
+
}
|
|
76
|
+
out += inner.slice(pos);
|
|
77
|
+
out += selfClosing ? "/>" : ">";
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 查找标签结束位置(尊重属性值内的引号,避免把 > 截断进属性值)
|
|
82
|
+
function findTagEnd(html, start) {
|
|
83
|
+
let j = start;
|
|
84
|
+
let quote = null;
|
|
85
|
+
while (j < html.length) {
|
|
86
|
+
const ch = html[j];
|
|
87
|
+
if (quote) {
|
|
88
|
+
if (ch === quote) {
|
|
89
|
+
quote = null;
|
|
90
|
+
}
|
|
91
|
+
} else if (ch === '"' || ch === "'") {
|
|
92
|
+
quote = ch;
|
|
93
|
+
} else if (ch === ">") {
|
|
94
|
+
return j;
|
|
22
95
|
}
|
|
23
|
-
|
|
24
|
-
|
|
96
|
+
j++;
|
|
97
|
+
}
|
|
98
|
+
return -1;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function isTagBoundary(html, pos) {
|
|
102
|
+
if (pos >= html.length) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
const ch = html[pos];
|
|
106
|
+
return ch === " " || ch === "\t" || ch === "\n" || ch === "\r" || ch === ">" || ch === "/";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 全页扫描:只在真实 <img> 标签上做懒加载,跳过脚本/样式/注释内容
|
|
110
|
+
function processHtml(html) {
|
|
111
|
+
const parts = [];
|
|
112
|
+
let i = 0;
|
|
113
|
+
const n = html.length;
|
|
114
|
+
const lower = html.toLowerCase();
|
|
115
|
+
while (i < n) {
|
|
116
|
+
const ch = html[i];
|
|
117
|
+
if (ch !== "<") {
|
|
118
|
+
parts.push(ch);
|
|
119
|
+
i++;
|
|
120
|
+
continue;
|
|
25
121
|
}
|
|
26
|
-
//
|
|
27
|
-
if (
|
|
28
|
-
|
|
122
|
+
// HTML 注释
|
|
123
|
+
if (html.startsWith("<!--", i)) {
|
|
124
|
+
const end = html.indexOf("-->", i);
|
|
125
|
+
if (end === -1) {
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
parts.push(html.slice(i, end + 3));
|
|
129
|
+
i = end + 3;
|
|
130
|
+
continue;
|
|
29
131
|
}
|
|
30
|
-
|
|
31
|
-
if (
|
|
32
|
-
|
|
132
|
+
// <script>…</script>:整段跳过,避免误处理模板字符串/字符串里的 <img
|
|
133
|
+
if (lower.startsWith("<script", i) && isTagBoundary(html, i + 7)) {
|
|
134
|
+
const end = findTagEnd(html, i);
|
|
135
|
+
if (end === -1) {
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
parts.push(html.slice(i, end + 1));
|
|
139
|
+
i = end + 1;
|
|
140
|
+
const close = lower.indexOf("</script", i);
|
|
141
|
+
if (close === -1) {
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
const closeEnd = html.indexOf(">", close);
|
|
145
|
+
if (closeEnd === -1) {
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
parts.push(html.slice(i, closeEnd + 1));
|
|
149
|
+
i = closeEnd + 1;
|
|
150
|
+
continue;
|
|
33
151
|
}
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
newClass += 'lazy';
|
|
41
|
-
if (value) {
|
|
42
|
-
return ori.replace('class="' + value, 'class="' + newClass);
|
|
43
|
-
} else {
|
|
44
|
-
return ori.replace('class="', 'class="' + newClass);
|
|
152
|
+
// <style>…</style>:整段跳过
|
|
153
|
+
if (lower.startsWith("<style", i) && isTagBoundary(html, i + 6)) {
|
|
154
|
+
const end = findTagEnd(html, i);
|
|
155
|
+
if (end === -1) {
|
|
156
|
+
break;
|
|
45
157
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
158
|
+
parts.push(html.slice(i, end + 1));
|
|
159
|
+
i = end + 1;
|
|
160
|
+
const close = lower.indexOf("</style", i);
|
|
161
|
+
if (close === -1) {
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
const closeEnd = html.indexOf(">", close);
|
|
165
|
+
if (closeEnd === -1) {
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
parts.push(html.slice(i, closeEnd + 1));
|
|
169
|
+
i = closeEnd + 1;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
// <img …>
|
|
173
|
+
if (lower.startsWith("<img", i) && isTagBoundary(html, i + 4)) {
|
|
174
|
+
const end = findTagEnd(html, i);
|
|
175
|
+
if (end === -1) {
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
parts.push(processImgTag(html.slice(i, end + 1)));
|
|
179
|
+
i = end + 1;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
parts.push(ch);
|
|
183
|
+
i++;
|
|
184
|
+
}
|
|
185
|
+
return parts.join("");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function lazyProcess(htmlContent) {
|
|
189
|
+
// 短路:无 <img 的页面(如 404)无需扫描
|
|
190
|
+
if (typeof htmlContent !== "string" || !/<img/i.test(htmlContent)) {
|
|
191
|
+
return htmlContent;
|
|
192
|
+
}
|
|
193
|
+
return processHtml(htmlContent);
|
|
52
194
|
}
|
|
53
195
|
|
|
54
|
-
module.exports.processSite = function(htmlContent) {
|
|
196
|
+
module.exports.processSite = function (htmlContent) {
|
|
55
197
|
return lazyProcess.call(this, htmlContent);
|
|
56
198
|
};
|
|
199
|
+
|
|
200
|
+
module.exports.lazyProcess = lazyProcess;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// 与 layout/_partial/scripts/defines.ejs 中旧的白名单保持一致(客户端通过 ctx.icons 读取)。
|
|
6
6
|
hexo.extend.generator.register('stellar_icons', function () {
|
|
7
7
|
const icons = (this.theme.config && this.theme.config.icons) || {};
|
|
8
|
-
const keys = ['
|
|
8
|
+
const keys = ['default:tocomment', 'default:loading-spinner', 'default:warning', 'weibo:repeat', 'weibo:like'];
|
|
9
9
|
const out = {};
|
|
10
10
|
// 去除 SVG 注释(避免 <!-- / </script> 解析风险),再转义 <
|
|
11
11
|
for (const k of keys) {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { stripHTML } = require('hexo-util');
|
|
4
|
+
|
|
5
|
+
// 文章一行小字取值(hero 卡片与置顶轮播共用,未来可复用于其它场景):
|
|
6
|
+
// subtitle > description > excerpt/content 前 length 字(去 HTML、压缩空白、截断;省略号由 CSS 单行处理),空则返回空串。
|
|
7
|
+
function subtitle(post, length = 50) {
|
|
8
|
+
if (post == null) {
|
|
9
|
+
return '';
|
|
10
|
+
}
|
|
11
|
+
if (post.subtitle != null && String(post.subtitle).length > 0) {
|
|
12
|
+
return post.subtitle;
|
|
13
|
+
}
|
|
14
|
+
if (post.description != null && String(post.description).length > 0) {
|
|
15
|
+
return post.description;
|
|
16
|
+
}
|
|
17
|
+
const text = stripHTML(post.excerpt || post.content || '').replace(/\s+/g, ' ').trim();
|
|
18
|
+
if (text.length === 0) {
|
|
19
|
+
return '';
|
|
20
|
+
}
|
|
21
|
+
return text.length > length ? text.slice(0, length) : text;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = { subtitle };
|
|
@@ -11,12 +11,13 @@
|
|
|
11
11
|
|
|
12
12
|
module.exports = ctx => function(args, content) {
|
|
13
13
|
args = ctx.args.map(args, ['bg', 'avatar', 'link'], ['title', 'subtitle'])
|
|
14
|
+
var bg = args.bg ? args.bg : ctx.theme.config.default.banner
|
|
14
15
|
var el = ''
|
|
15
|
-
el += `<div class="tag-plugin banner">`
|
|
16
|
+
el += `<div class="tag-plugin banner" style="--bg-url:url('${bg.replace(/'/g, '%27')}')">`
|
|
16
17
|
// bg
|
|
17
|
-
el += `<img class="lazy bg" data-src="${
|
|
18
|
+
el += `<img class="lazy bg" data-src="${bg}">`
|
|
18
19
|
// content
|
|
19
|
-
el += `<div class="content">`
|
|
20
|
+
el += `<div class="content" data-text-adaptive="split">`
|
|
20
21
|
// content.top
|
|
21
22
|
el += `<div class="top">`
|
|
22
23
|
// content.top.back
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// 背景图覆盖层通用能力:同图渐变模糊层 + 黑色渐变蒙版 + hover 变暗
|
|
2
|
+
// $url-var: 背景图 CSS 变量(--cover-url / --pin-cover-url / --bg-url),模糊层取同图背景
|
|
3
|
+
// $sides: top | bottom | both —— 文字所在边缘;both 依赖宿主内的 .banner-mask-top/bottom 蒙版元素
|
|
4
|
+
// $layer: 覆盖层宿主选择器(相对当前元素),缺省为当前元素自身;图片等 hover 目标由调用方按 DOM 自行接入
|
|
5
|
+
cover-overlay($url-var, $sides = bottom, $layer = null)
|
|
6
|
+
$host = $layer ? $layer : '&'
|
|
7
|
+
// 统一默认观感变量(与文章列表 hero 封面一致)
|
|
8
|
+
--img-br: 100%
|
|
9
|
+
--img-sat: 100%
|
|
10
|
+
--blur-px: 1em
|
|
11
|
+
--blur-height: 128px
|
|
12
|
+
--blur-sat: 120%
|
|
13
|
+
--cover-zoom: 1.05
|
|
14
|
+
// hover 整体变暗:亮度 75%、饱和度 120%,模糊层饱和度提升至 200%
|
|
15
|
+
&:hover
|
|
16
|
+
--img-br: 75%
|
|
17
|
+
--img-sat: 120%
|
|
18
|
+
--blur-sat: 200%
|
|
19
|
+
if $sides == both
|
|
20
|
+
// 双侧::before 顶部模糊层 + :after 底部模糊层,黑色蒙版由 .banner-mask-top/bottom 承担
|
|
21
|
+
{$host}:before, {$host}:after
|
|
22
|
+
content: ''
|
|
23
|
+
position: absolute
|
|
24
|
+
top: 0
|
|
25
|
+
left: 0
|
|
26
|
+
width: 100%
|
|
27
|
+
height: 100%
|
|
28
|
+
z-index: 0
|
|
29
|
+
background-image: $url-var
|
|
30
|
+
background-size: cover
|
|
31
|
+
background-position: center
|
|
32
|
+
filter: brightness(var(--img-br)) saturate(var(--img-sat)) blur(var(--blur-px)) saturate(var(--blur-sat))
|
|
33
|
+
// 常驻恒等 transform:hover 放大时 Safari 正确应用父级圆角裁剪(同 list.styl 注释)
|
|
34
|
+
transform: translateZ(0)
|
|
35
|
+
pointer-events: none
|
|
36
|
+
transition: all .2s ease-out, transform .5s ease-out
|
|
37
|
+
-moz-transition: all .2s ease-out, transform .5s ease-out
|
|
38
|
+
-webkit-transition: all .2s ease-out, transform .5s ease-out
|
|
39
|
+
-o-transition: all .2s ease-out, transform .5s ease-out
|
|
40
|
+
{$host}:before
|
|
41
|
+
mask: linear-gradient(black, rgba(black, 0.75) calc(var(--blur-height-top, var(--blur-height)) * 0.5), transparent var(--blur-height-top, var(--blur-height)))
|
|
42
|
+
-webkit-mask: linear-gradient(black, rgba(black, 0.75) calc(var(--blur-height-top, var(--blur-height)) * 0.5), transparent var(--blur-height-top, var(--blur-height)))
|
|
43
|
+
{$host}:after
|
|
44
|
+
mask: linear-gradient(to top, black, rgba(black, 0.75) calc(var(--blur-height-bottom, var(--blur-height)) * 0.5), transparent var(--blur-height-bottom, var(--blur-height)))
|
|
45
|
+
-webkit-mask: linear-gradient(to top, black, rgba(black, 0.75) calc(var(--blur-height-bottom, var(--blur-height)) * 0.5), transparent var(--blur-height-bottom, var(--blur-height)))
|
|
46
|
+
if $layer
|
|
47
|
+
&:hover
|
|
48
|
+
{$host}:before, {$host}:after
|
|
49
|
+
transform: translateZ(0) scale(var(--cover-zoom))
|
|
50
|
+
else
|
|
51
|
+
{$host}:hover:before, {$host}:hover:after
|
|
52
|
+
transform: translateZ(0) scale(var(--cover-zoom))
|
|
53
|
+
// 黑色渐变蒙版:文字所在边缘不透明度约 0.25,垂直中线为 0
|
|
54
|
+
{$host} .banner-mask
|
|
55
|
+
position: absolute
|
|
56
|
+
inset: 0
|
|
57
|
+
z-index: 1
|
|
58
|
+
pointer-events: none
|
|
59
|
+
&.banner-mask-top
|
|
60
|
+
background-image: linear-gradient(rgba(0,0,0,0.25), rgba(0,0,0,0) 50%)
|
|
61
|
+
&.banner-mask-bottom
|
|
62
|
+
background-image: linear-gradient(rgba(0,0,0,0) 50%, rgba(0,0,0,0.25))
|
|
63
|
+
else
|
|
64
|
+
// 单侧::before 同图模糊层 + :after 黑色渐变蒙版
|
|
65
|
+
{$host}:before
|
|
66
|
+
content: ''
|
|
67
|
+
position: absolute
|
|
68
|
+
top: 0
|
|
69
|
+
left: 0
|
|
70
|
+
width: 100%
|
|
71
|
+
height: 100%
|
|
72
|
+
z-index: 1
|
|
73
|
+
background-image: $url-var
|
|
74
|
+
background-size: cover
|
|
75
|
+
background-position: center
|
|
76
|
+
filter: brightness(var(--img-br)) saturate(var(--img-sat)) blur(var(--blur-px)) saturate(var(--blur-sat))
|
|
77
|
+
// 常驻恒等 transform:hover 放大时 Safari 正确应用父级圆角裁剪(同 list.styl 注释)
|
|
78
|
+
transform: translateZ(0)
|
|
79
|
+
pointer-events: none
|
|
80
|
+
transition: all .2s ease-out, transform .5s ease-out
|
|
81
|
+
-moz-transition: all .2s ease-out, transform .5s ease-out
|
|
82
|
+
-webkit-transition: all .2s ease-out, transform .5s ease-out
|
|
83
|
+
-o-transition: all .2s ease-out, transform .5s ease-out
|
|
84
|
+
if $sides == top
|
|
85
|
+
{$host}:before
|
|
86
|
+
mask: linear-gradient(black, rgba(black, 0.75) calc(var(--blur-height-top, var(--blur-height)) * 0.5), transparent var(--blur-height-top, var(--blur-height)))
|
|
87
|
+
-webkit-mask: linear-gradient(black, rgba(black, 0.75) calc(var(--blur-height-top, var(--blur-height)) * 0.5), transparent var(--blur-height-top, var(--blur-height)))
|
|
88
|
+
else
|
|
89
|
+
{$host}:before
|
|
90
|
+
mask: linear-gradient(to top, black, rgba(black, 0.75) calc(var(--blur-height-bottom, var(--blur-height)) * 0.5), transparent var(--blur-height-bottom, var(--blur-height)))
|
|
91
|
+
-webkit-mask: linear-gradient(to top, black, rgba(black, 0.75) calc(var(--blur-height-bottom, var(--blur-height)) * 0.5), transparent var(--blur-height-bottom, var(--blur-height)))
|
|
92
|
+
if $layer
|
|
93
|
+
&:hover
|
|
94
|
+
{$host}:before
|
|
95
|
+
transform: translateZ(0) scale(var(--cover-zoom))
|
|
96
|
+
else
|
|
97
|
+
{$host}:hover:before
|
|
98
|
+
transform: translateZ(0) scale(var(--cover-zoom))
|
|
99
|
+
{$host}:after
|
|
100
|
+
content: ''
|
|
101
|
+
position: absolute
|
|
102
|
+
top: 0
|
|
103
|
+
left: 0
|
|
104
|
+
width: 100%
|
|
105
|
+
height: 100%
|
|
106
|
+
z-index: 1
|
|
107
|
+
pointer-events: none
|
|
108
|
+
if $sides == top
|
|
109
|
+
background-image: linear-gradient(rgba(0,0,0,0.25), rgba(0,0,0,0) 50%)
|
|
110
|
+
else
|
|
111
|
+
background-image: linear-gradient(rgba(0,0,0,0) 50%, rgba(0,0,0,0.25))
|
|
@@ -15,6 +15,23 @@ h5
|
|
|
15
15
|
h6
|
|
16
16
|
font-size: 'calc(%s + 0px)' % var(--fsp)
|
|
17
17
|
|
|
18
|
+
// story 文章段落标题(h2):居中 + 两侧 accent 斜杠装饰
|
|
19
|
+
// 专栏列表页专栏标题(.topic-title)与 story 文章 h2 共用
|
|
20
|
+
story-title()
|
|
21
|
+
color: var(--text)
|
|
22
|
+
line-height: 1.2
|
|
23
|
+
text-align: center
|
|
24
|
+
&:before,&:after
|
|
25
|
+
content: ''
|
|
26
|
+
display: inline-block
|
|
27
|
+
width: 12px
|
|
28
|
+
height: 1.2em
|
|
29
|
+
background: $c-accent
|
|
30
|
+
transform: skewX(-20deg)
|
|
31
|
+
margin: 0 0.5em
|
|
32
|
+
vertical-align: top
|
|
33
|
+
position: relative
|
|
34
|
+
|
|
18
35
|
// 次级段落字号
|
|
19
36
|
.fs15
|
|
20
37
|
--fsp: $fs-15
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
.l_body
|
|
2
2
|
display: grid
|
|
3
3
|
grid-template-columns: 1fr minmax(200px,var(--width-main)) 1fr
|
|
4
|
-
grid-gap: calc(var(--gap-
|
|
5
|
-
@media screen and (max-width: $device-desktop)
|
|
6
|
-
grid-gap: calc(var(--gap-margin) * 2)
|
|
4
|
+
grid-gap: calc(var(--gap-page) * 1)
|
|
7
5
|
margin: auto
|
|
8
6
|
font-size: var(--fsp)
|
|
9
7
|
|
|
@@ -13,36 +11,34 @@
|
|
|
13
11
|
position: sticky
|
|
14
12
|
position: -webkit-sticky
|
|
15
13
|
// 侧边栏容器宽度
|
|
16
|
-
--width-sidebar: calc(var(--gap-
|
|
14
|
+
--width-sidebar: calc(var(--gap-base) * 4 + var(--side-content-width))
|
|
17
15
|
width: var(--width-sidebar)
|
|
18
16
|
|
|
19
17
|
.l_body .l_left
|
|
20
18
|
justify-self: right
|
|
21
|
-
top: var(--gap-
|
|
19
|
+
top: var(--gap-page)
|
|
22
20
|
.l_body .l_right
|
|
23
21
|
justify-self: left
|
|
24
|
-
|
|
22
|
+
width: calc(var(--width-sidebar) + var(--rightbar-width-extra))
|
|
23
|
+
max-height: calc(100% - var(--gap-page) * 2)
|
|
25
24
|
.widgets
|
|
26
25
|
height: 100%
|
|
27
26
|
overflow visible
|
|
28
27
|
|
|
29
28
|
// 平板布局
|
|
30
29
|
@media screen and (max-width: $device-laptop)
|
|
31
|
-
.l_body .l_right
|
|
32
|
-
--gap-margin: 16px
|
|
33
30
|
.laptop-only
|
|
34
31
|
display: block !important
|
|
35
32
|
.l_body
|
|
36
|
-
grid-gap: calc(var(--gap-margin) * 1)
|
|
37
33
|
.l_right
|
|
38
34
|
position: fixed
|
|
39
|
-
transform: translateX(
|
|
35
|
+
transform: translateX(calc(100% + var(--inset) * 2))
|
|
40
36
|
transition: transform .38s ease-out
|
|
41
37
|
margin: 0
|
|
42
38
|
--inset: 8px
|
|
43
39
|
right: var(--inset)
|
|
44
|
-
top: var(--gap-
|
|
45
|
-
max-height: calc(100vh - var(--gap-
|
|
40
|
+
top: var(--gap-page)
|
|
41
|
+
max-height: calc(100vh - var(--gap-page) * 2)
|
|
46
42
|
box-shadow: $boxshadow-card-float
|
|
47
43
|
z-index: 10
|
|
48
44
|
background: var(--background)
|
|
@@ -66,7 +62,7 @@
|
|
|
66
62
|
display: block
|
|
67
63
|
.l_left
|
|
68
64
|
position: fixed
|
|
69
|
-
transform: translateX(-
|
|
65
|
+
transform: translateX(calc(-100% - var(--inset) * 2))
|
|
70
66
|
transition: transform .38s ease-out
|
|
71
67
|
margin: 0
|
|
72
68
|
--inset: 8px
|