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.
Files changed (70) hide show
  1. package/.claude/skills/stellar-theme-dev/SKILL.md +2 -2
  2. package/CHANGELOG.md +60 -0
  3. package/CONTRIBUTING.md +2 -1
  4. package/README.md +50 -21
  5. package/README_EN.md +113 -0
  6. package/_config.yml +11 -6
  7. package/_data/icons.yml +0 -2
  8. package/layout/_partial/cover/index.ejs +0 -3
  9. package/layout/_partial/main/navbar/article_banner.ejs +30 -27
  10. package/layout/_partial/main/pin_slider.ejs +59 -28
  11. package/layout/_partial/main/post_list/latest_post_card.ejs +25 -0
  12. package/layout/_partial/main/post_list/post_card.ejs +16 -64
  13. package/layout/_partial/main/post_list/topic_card.ejs +26 -5
  14. package/layout/_partial/scripts/bootstrap.ejs +37 -0
  15. package/layout/_partial/scripts/defines.ejs +2 -1
  16. package/layout/_partial/scripts.ejs +9 -0
  17. package/layout/_partial/widgets/toc.ejs +2 -2
  18. package/layout/_plugins/adaptive_text.ejs +11 -0
  19. package/layout/_plugins/copycode.ejs +2 -2
  20. package/layout/_plugins/index.ejs +25 -0
  21. package/layout/_plugins/mermaid.ejs +1 -1
  22. package/layout/_plugins/scrollreveal.ejs +22 -16
  23. package/layout/_plugins/swiper.ejs +1 -1
  24. package/layout/index.ejs +10 -2
  25. package/layout/index_topic.ejs +4 -4
  26. package/package.json +3 -3
  27. package/scripts/filters/lib/img_lazyload.js +184 -40
  28. package/scripts/generators/stellar-icons.js +1 -1
  29. package/scripts/helpers/subtitle.js +8 -0
  30. package/scripts/lib/subtitle.js +24 -0
  31. package/scripts/tags/lib/banner.js +4 -3
  32. package/source/css/_common/cover-overlay.styl +111 -0
  33. package/source/css/_common/title.styl +17 -0
  34. package/source/css/_components/layout.styl +9 -13
  35. package/source/css/_components/list.styl +135 -64
  36. package/source/css/_components/main.styl +2 -2
  37. package/source/css/_components/pages/archives.styl +4 -1
  38. package/source/css/_components/pages/article-story.styl +1 -10
  39. package/source/css/_components/partial/article-banner.styl +61 -37
  40. package/source/css/_components/partial/article-tags.styl +4 -1
  41. package/source/css/_components/partial/bread-nav.styl +7 -14
  42. package/source/css/_components/partial/navbar.styl +1 -1
  43. package/source/css/_components/partial/post-panel.styl +76 -0
  44. package/source/css/_components/pin-slider.styl +38 -41
  45. package/source/css/_components/sidebar/footer.styl +1 -1
  46. package/source/css/_components/sidebar/logo.styl +2 -2
  47. package/source/css/_components/sidebar/search.styl +1 -1
  48. package/source/css/_components/sidebar/sidebar.styl +7 -7
  49. package/source/css/_components/tag-plugins/banner.styl +53 -11
  50. package/source/css/_components/tag-plugins/friends_posts.styl +5 -86
  51. package/source/css/_components/tag-plugins/gallery.styl +3 -0
  52. package/source/css/_components/tag-plugins/read/paper.styl +3 -3
  53. package/source/css/_components/tag-plugins/read/reel.styl +4 -4
  54. package/source/css/_components/tag-plugins/timeline.styl +4 -0
  55. package/source/css/_components/widgets/ghrepo.styl +4 -0
  56. package/source/css/_components/widgets/timeline.styl +2 -2
  57. package/source/css/_components/widgets/toc.styl +12 -6
  58. package/source/css/_components/widgets/widgets.styl +8 -7
  59. package/source/css/_custom.styl +8 -9
  60. package/source/css/_plugins/lazyload.styl +1 -0
  61. package/source/css/comments/waline.styl +24 -0
  62. package/source/js/color.js +325 -0
  63. package/source/js/main.js +2 -2
  64. package/source/js/plugins/adaptive-text.js +146 -0
  65. package/source/js/services/friends_and_posts.js +1 -1
  66. package/source/js/services/timeline.js +1 -1
  67. package/source/js/services/weibo.js +1 -1
  68. package/source/js/services.js +1 -1
  69. package/source/js/utils.js +14 -1
  70. package/layout/_partial/cover/post_cover.ejs +0 -37
@@ -1,56 +1,200 @@
1
1
  /**
2
- * img_lazyload.js v1 | https://github.com/xaoxuu/hexo-theme-stellar/
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
- 'use strict';
11
+ "use strict";
7
12
 
8
- // 懒加载强制开启:不再读取 enable 配置,no-lazy 是唯一例外
9
- function lazyProcess(htmlContent) {
10
- // 短路:无 <img 的页面(如 404)无需正则扫描
11
- if (typeof htmlContent !== 'string' || !/<img/i.test(htmlContent)) {
12
- return htmlContent;
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
- return htmlContent.replace(/<img(.*?)src="(.*?)"(.*?)>/gi, function(imgTag, src_before, src_value, src_after) {
15
- // 已由 tag 插件输出懒加载标记(data-src / data-srcset)的图片不重复处理
16
- if (/data-src/gi.test(imgTag)) {
17
- return imgTag;
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
- // 使用 srcset 的图片交给浏览器原生处理,避免占位图与 srcset 冲突
20
- if (/srcset=/gi.test(imgTag)) {
21
- return imgTag;
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
- if (/src="data:image(.*?)/gi.test(imgTag)) {
24
- return imgTag;
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
- // no-lazy 兼容 `no-lazy` / `no-lazy=""` 两种写法
27
- if (/\bno-lazy\b/gi.test(imgTag)) {
28
- return imgTag;
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
- var newImgTag = imgTag;
31
- if (newImgTag.includes(' class="') == false) {
32
- newImgTag = newImgTag.slice(0,4) + ' class=""' + newImgTag.slice(4);
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
- // class 中增加 lazy
35
- newImgTag = newImgTag.replace(/(.*?) class="(.*?)" (.*?)>/gi, function(ori, before, value, after){
36
- var newClass = value;
37
- if (newClass.length > 0) {
38
- newClass += ' ';
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
- const loadingImg = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAAaADAAQAAAABAAAAAQAAAADa6r/EAAAAC0lEQVQIHWNgAAIAAAUAAY27m/MAAAAASUVORK5CYII=';
49
- newImgTag = newImgTag.replace(src_value, loadingImg + '" data-src="' + src_value);
50
- return newImgTag;
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 = ['weibo:comment', 'default:loading-spinner', 'default:warning', 'weibo:repeat', 'weibo:like'];
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,8 @@
1
+ /* global hexo */
2
+ 'use strict';
3
+
4
+ const { subtitle } = require('../lib/subtitle');
5
+
6
+ hexo.extend.helper.register('subtitle', function(post) {
7
+ return subtitle(post);
8
+ });
@@ -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="${args.bg ? args.bg : ctx.theme.config.default.banner}">`
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-margin) * 4)
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-margin) * 2 + var(--gap-padding) * 2 + var(--side-content-width))
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-margin)
19
+ top: var(--gap-page)
22
20
  .l_body .l_right
23
21
  justify-self: left
24
- max-height: calc(100% - var(--gap-margin) * 2)
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(320px)
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-margin)
45
- max-height: calc(100vh - var(--gap-margin) * 2)
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(-320px)
65
+ transform: translateX(calc(-100% - var(--inset) * 2))
70
66
  transition: transform .38s ease-out
71
67
  margin: 0
72
68
  --inset: 8px