hexo-theme-stellar 1.41.0 → 1.42.0
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 +1 -2
- package/CHANGELOG.md +49 -0
- package/CONTRIBUTING.md +2 -1
- package/README.md +50 -21
- package/README_EN.md +113 -0
- package/_config.yml +8 -3
- 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 +35 -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/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/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/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/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
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
// 文字自适应颜色通用能力(挂载 window.stellar.color)
|
|
2
|
+
// 由 layout/_plugins/adaptive-text.ejs 按需懒加载;也可供其他组件直接调用。
|
|
3
|
+
// 样式1(contrast):背景深色 → 白色文字,背景浅色 → 深色文字。
|
|
4
|
+
// 样式2(theme):以背景图平均色为基色,背景偏暗 → lighten 到高亮度,
|
|
5
|
+
// 背景偏亮 → darken 到低亮度(保留色相与饱和度)。
|
|
6
|
+
(function () {
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
var color = {};
|
|
10
|
+
|
|
11
|
+
// 颜色字符串 → {r,g,b,a}(a 为 0~1 透明度,默认 1);
|
|
12
|
+
// 支持 #rgb/#rrggbb/#rrggbbaa、rgb()/rgba()、hsl()/hsla()(逗号与空格/斜杠语法)。
|
|
13
|
+
// 解析失败返回 null。
|
|
14
|
+
color.parse = function (input) {
|
|
15
|
+
if (input == null) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
var str = String(input).trim();
|
|
19
|
+
var a = 1;
|
|
20
|
+
if (str.charAt(0) === '#') {
|
|
21
|
+
var hex = str.slice(1);
|
|
22
|
+
if (hex.length === 3 || hex.length === 4) {
|
|
23
|
+
hex = hex.split('').map(function (c) {
|
|
24
|
+
return c + c;
|
|
25
|
+
}).join('');
|
|
26
|
+
}
|
|
27
|
+
if (hex.length === 6 || hex.length === 8) {
|
|
28
|
+
var r = parseInt(hex.slice(0, 2), 16);
|
|
29
|
+
var g = parseInt(hex.slice(2, 4), 16);
|
|
30
|
+
var b = parseInt(hex.slice(4, 6), 16);
|
|
31
|
+
if (hex.length === 8) {
|
|
32
|
+
a = parseInt(hex.slice(6, 8), 16) / 255;
|
|
33
|
+
}
|
|
34
|
+
if (!isNaN(r) && !isNaN(g) && !isNaN(b)) {
|
|
35
|
+
return { r: r, g: g, b: b, a: a };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
var m = str.match(/rgba?\(\s*([\d.]+)\s*[,\s]\s*([\d.]+)\s*[,\s]\s*([\d.]+)(?:\s*[,\s/]\s*([\d.]+%?))?\s*\)/i);
|
|
41
|
+
if (m) {
|
|
42
|
+
var r2 = Number(m[1]);
|
|
43
|
+
var g2 = Number(m[2]);
|
|
44
|
+
var b2 = Number(m[3]);
|
|
45
|
+
if (!isNaN(r2) && !isNaN(g2) && !isNaN(b2)) {
|
|
46
|
+
if (m[4] != null) {
|
|
47
|
+
a = m[4].charAt(m[4].length - 1) === '%' ? Number(m[4].slice(0, -1)) / 100 : Number(m[4]);
|
|
48
|
+
}
|
|
49
|
+
return { r: r2, g: g2, b: b2, a: a };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
var hm = str.match(/hsla?\(\s*([\d.]+)(?:deg)?\s*[,\s]\s*([\d.]+%?)\s*[,\s]\s*([\d.]+%?)(?:\s*[,\s/]\s*([\d.]+%?))?\s*\)/i);
|
|
53
|
+
if (hm) {
|
|
54
|
+
var hue = Number(hm[1]);
|
|
55
|
+
var sat = hm[2].charAt(hm[2].length - 1) === '%' ? Number(hm[2].slice(0, -1)) / 100 : Number(hm[2]);
|
|
56
|
+
var lig = hm[3].charAt(hm[3].length - 1) === '%' ? Number(hm[3].slice(0, -1)) / 100 : Number(hm[3]);
|
|
57
|
+
if (!isNaN(hue) && !isNaN(sat) && !isNaN(lig)) {
|
|
58
|
+
if (hm[4] != null) {
|
|
59
|
+
a = hm[4].charAt(hm[4].length - 1) === '%' ? Number(hm[4].slice(0, -1)) / 100 : Number(hm[4]);
|
|
60
|
+
}
|
|
61
|
+
var h = ((hue % 360) + 360) % 360 / 360;
|
|
62
|
+
var rgb = hslToRgb(h, Math.max(0, Math.min(1, sat)), Math.max(0, Math.min(1, lig)));
|
|
63
|
+
return { r: rgb.r, g: rgb.g, b: rgb.b, a: a };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// WCAG 相对亮度(0~1)
|
|
70
|
+
color.luminance = function (rgb) {
|
|
71
|
+
if (rgb == null) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
var linearize = function (v) {
|
|
75
|
+
v = v / 255;
|
|
76
|
+
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
|
77
|
+
};
|
|
78
|
+
return 0.2126 * linearize(rgb.r) + 0.7152 * linearize(rgb.g) + 0.0722 * linearize(rgb.b);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// 亮度是否偏暗,threshold 默认 0.5
|
|
82
|
+
color.isDark = function (rgb, threshold) {
|
|
83
|
+
var lum = color.luminance(rgb);
|
|
84
|
+
if (lum == null) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
return lum < (threshold == null ? 0.5 : threshold);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
function rgbToHsl(rgb) {
|
|
91
|
+
var r = rgb.r / 255;
|
|
92
|
+
var g = rgb.g / 255;
|
|
93
|
+
var b = rgb.b / 255;
|
|
94
|
+
var max = Math.max(r, g, b);
|
|
95
|
+
var min = Math.min(r, g, b);
|
|
96
|
+
var h = 0;
|
|
97
|
+
var s = 0;
|
|
98
|
+
var l = (max + min) / 2;
|
|
99
|
+
if (max !== min) {
|
|
100
|
+
var d = max - min;
|
|
101
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
102
|
+
switch (max) {
|
|
103
|
+
case r:
|
|
104
|
+
h = (g - b) / d + (g < b ? 6 : 0);
|
|
105
|
+
break;
|
|
106
|
+
case g:
|
|
107
|
+
h = (b - r) / d + 2;
|
|
108
|
+
break;
|
|
109
|
+
default:
|
|
110
|
+
h = (r - g) / d + 4;
|
|
111
|
+
}
|
|
112
|
+
h /= 6;
|
|
113
|
+
}
|
|
114
|
+
return { h: h, s: s, l: l };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function hslToRgb(h, s, l) {
|
|
118
|
+
if (s === 0) {
|
|
119
|
+
var gray = Math.round(l * 255);
|
|
120
|
+
return { r: gray, g: gray, b: gray };
|
|
121
|
+
}
|
|
122
|
+
var hue2rgb = function (p, q, t) {
|
|
123
|
+
if (t < 0) {
|
|
124
|
+
t += 1;
|
|
125
|
+
}
|
|
126
|
+
if (t > 1) {
|
|
127
|
+
t -= 1;
|
|
128
|
+
}
|
|
129
|
+
if (t < 1 / 6) {
|
|
130
|
+
return p + (q - p) * 6 * t;
|
|
131
|
+
}
|
|
132
|
+
if (t < 1 / 2) {
|
|
133
|
+
return q;
|
|
134
|
+
}
|
|
135
|
+
if (t < 2 / 3) {
|
|
136
|
+
return p + (q - p) * (2 / 3 - t) * 6;
|
|
137
|
+
}
|
|
138
|
+
return p;
|
|
139
|
+
};
|
|
140
|
+
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
141
|
+
var p = 2 * l - q;
|
|
142
|
+
return {
|
|
143
|
+
r: Math.round(hue2rgb(p, q, h + 1 / 3) * 255),
|
|
144
|
+
g: Math.round(hue2rgb(p, q, h) * 255),
|
|
145
|
+
b: Math.round(hue2rgb(p, q, h - 1 / 3) * 255)
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function rgbString(rgb) {
|
|
150
|
+
return 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 设定 HSL 明度目标(0~1),保留色相与饱和度
|
|
154
|
+
color.withLightness = function (rgb, lightness) {
|
|
155
|
+
if (rgb == null) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
var hsl = rgbToHsl(rgb);
|
|
159
|
+
var target = Math.max(0, Math.min(1, lightness));
|
|
160
|
+
return rgbString(hslToRgb(hsl.h, hsl.s, target));
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// 浅色化:把明度调整到 lightness(默认 0.85)
|
|
164
|
+
color.lighten = function (rgb, lightness) {
|
|
165
|
+
return color.withLightness(rgb, lightness == null ? 0.85 : lightness);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// 深色化:把明度调整到 lightness(默认 0.3)
|
|
169
|
+
color.darken = function (rgb, lightness) {
|
|
170
|
+
return color.withLightness(rgb, lightness == null ? 0.3 : lightness);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// 主题色饱和度增强:平均色被大面积中性色盖过时饱和度极低(色相仍有效),
|
|
174
|
+
// 把低饱和度抬升到最低可见值(保留色相),让主题文字带出图片的主色倾向;
|
|
175
|
+
// 完全中性(无可靠色相)与已足够饱和的颜色不做处理。
|
|
176
|
+
color.enhanceSaturation = function (rgb, options) {
|
|
177
|
+
if (rgb == null) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
var opts = options || {};
|
|
181
|
+
var minS = opts.minSaturation == null ? 0.3 : opts.minSaturation;
|
|
182
|
+
var boostBelow = opts.boostBelow == null ? 0.2 : opts.boostBelow;
|
|
183
|
+
var epsilon = opts.neutralEpsilon == null ? 0.02 : opts.neutralEpsilon;
|
|
184
|
+
var hsl = rgbToHsl(rgb);
|
|
185
|
+
if (hsl.s >= boostBelow || hsl.s < epsilon) {
|
|
186
|
+
return { r: rgb.r, g: rgb.g, b: rgb.b, a: rgb.a == null ? 1 : rgb.a };
|
|
187
|
+
}
|
|
188
|
+
var out = hslToRgb(hsl.h, Math.max(hsl.s, minS), hsl.l);
|
|
189
|
+
return { r: out.r, g: out.g, b: out.b, a: rgb.a == null ? 1 : rgb.a };
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// 自适应文字颜色
|
|
193
|
+
// bg:{r,g,b} 或颜色字符串
|
|
194
|
+
// options:
|
|
195
|
+
// style 'theme'(默认)/ 'contrast'
|
|
196
|
+
// threshold 明暗判定阈值,默认按 effectiveThreshold(彩色背景 0.65 / 中性背景 0.6,
|
|
197
|
+
// 偏向采纳浅色文字,避免中灰背景频繁翻转)
|
|
198
|
+
// lightColor 样式1 深色背景时的文字色,默认 #ffffff
|
|
199
|
+
// darkColor 样式1 浅色背景时的文字色,默认 #111111
|
|
200
|
+
// lightLightness 样式2 深色背景时的目标明度,默认 0.85
|
|
201
|
+
// darkLightness 样式2 浅色背景时的目标明度,默认 0.3
|
|
202
|
+
// saturationScale 样式2 饱和度缩放(0~1,默认 1):调小更接近黑白,仅保留一点主色倾向
|
|
203
|
+
color.adaptiveTextColor = function (bg, options) {
|
|
204
|
+
var opts = options || {};
|
|
205
|
+
var rgb = (bg != null && typeof bg === 'object') ? bg : color.parse(bg);
|
|
206
|
+
if (rgb == null) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
var style = opts.style || 'theme';
|
|
210
|
+
var threshold = opts.threshold == null ? color.effectiveThreshold(rgb) : opts.threshold;
|
|
211
|
+
var dark = color.isDark(rgb, threshold);
|
|
212
|
+
if (style === 'contrast') {
|
|
213
|
+
return dark ? (opts.lightColor || '#ffffff') : (opts.darkColor || '#111111');
|
|
214
|
+
}
|
|
215
|
+
var base = color.enhanceSaturation(rgb);
|
|
216
|
+
var hsl = rgbToHsl(base);
|
|
217
|
+
if (opts.saturationScale != null) {
|
|
218
|
+
hsl.s = hsl.s * Math.max(0, Math.min(1, opts.saturationScale));
|
|
219
|
+
}
|
|
220
|
+
var target = dark
|
|
221
|
+
? (opts.lightLightness == null ? 0.85 : opts.lightLightness)
|
|
222
|
+
: (opts.darkLightness == null ? 0.3 : opts.darkLightness);
|
|
223
|
+
return rgbString(hslToRgb(hsl.h, hsl.s, target));
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// 有效明暗阈值:彩色背景(饱和度 > 0.2)更偏向浅色文字,阈值上浮 0.05;
|
|
227
|
+
// 中性灰背景保持基础阈值,避免浅灰图配浅字。
|
|
228
|
+
color.effectiveThreshold = function (rgb, base) {
|
|
229
|
+
var threshold = base == null ? 0.6 : base;
|
|
230
|
+
var hsl = rgbToHsl(rgb);
|
|
231
|
+
return hsl.s > 0.2 ? threshold + 0.05 : threshold;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// 按平均透明度向背景色混合:透明像素不再把平均色拉向黑色,
|
|
235
|
+
// 而是按实际渲染背景合成(options.background 为 {r,g,b} 或颜色字符串)。
|
|
236
|
+
// 不传背景或完全不透明时返回原 RGB。
|
|
237
|
+
color.blendToBackground = function (rgb, background) {
|
|
238
|
+
if (rgb == null) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
var alpha = rgb.a == null ? 255 : rgb.a;
|
|
242
|
+
if (!background || alpha >= 255) {
|
|
243
|
+
return { r: rgb.r, g: rgb.g, b: rgb.b };
|
|
244
|
+
}
|
|
245
|
+
var bgRgb = (background != null && typeof background === 'object') ? background : color.parse(background);
|
|
246
|
+
if (bgRgb == null) {
|
|
247
|
+
return { r: rgb.r, g: rgb.g, b: rgb.b };
|
|
248
|
+
}
|
|
249
|
+
var k = alpha / 255;
|
|
250
|
+
var inv = 1 - k;
|
|
251
|
+
return {
|
|
252
|
+
r: Math.round(rgb.r * k + bgRgb.r * inv),
|
|
253
|
+
g: Math.round(rgb.g * k + bgRgb.g * inv),
|
|
254
|
+
b: Math.round(rgb.b * k + bgRgb.b * inv)
|
|
255
|
+
};
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// 背景图平均色:等比缩至最长边 ≤ size(默认 64px)后 canvas 取 RGB 均值与平均透明度。
|
|
259
|
+
// 按 URL 缓存原始均值(含透明度);CORS、解码失败等异常返回 null(由调用方回退 CSS 默认色)。
|
|
260
|
+
var averageCache = {};
|
|
261
|
+
|
|
262
|
+
color.getAverageColor = function (src, options) {
|
|
263
|
+
var opts = options || {};
|
|
264
|
+
var size = opts.size || 64;
|
|
265
|
+
if (averageCache[src]) {
|
|
266
|
+
return averageCache[src].then(function (raw) {
|
|
267
|
+
return color.blendToBackground(raw, opts.background);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
var promise = new Promise(function (resolve) {
|
|
271
|
+
var img = new Image();
|
|
272
|
+
img.crossOrigin = 'anonymous';
|
|
273
|
+
img.onload = function () {
|
|
274
|
+
try {
|
|
275
|
+
var w = img.naturalWidth || img.width;
|
|
276
|
+
var h = img.naturalHeight || img.height;
|
|
277
|
+
if (!w || !h) {
|
|
278
|
+
resolve(null);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
var scale = Math.min(1, size / Math.max(w, h));
|
|
282
|
+
var cw = Math.max(1, Math.round(w * scale));
|
|
283
|
+
var ch = Math.max(1, Math.round(h * scale));
|
|
284
|
+
var canvas = document.createElement('canvas');
|
|
285
|
+
canvas.width = cw;
|
|
286
|
+
canvas.height = ch;
|
|
287
|
+
var ctx = canvas.getContext('2d');
|
|
288
|
+
ctx.drawImage(img, 0, 0, cw, ch);
|
|
289
|
+
var data = ctx.getImageData(0, 0, cw, ch).data;
|
|
290
|
+
var total = cw * ch;
|
|
291
|
+
var r = 0;
|
|
292
|
+
var g = 0;
|
|
293
|
+
var b = 0;
|
|
294
|
+
var a = 0;
|
|
295
|
+
for (var i = 0; i < data.length; i += 4) {
|
|
296
|
+
r += data[i];
|
|
297
|
+
g += data[i + 1];
|
|
298
|
+
b += data[i + 2];
|
|
299
|
+
a += data[i + 3];
|
|
300
|
+
}
|
|
301
|
+
resolve({
|
|
302
|
+
r: Math.round(r / total),
|
|
303
|
+
g: Math.round(g / total),
|
|
304
|
+
b: Math.round(b / total),
|
|
305
|
+
a: Math.round(a / total)
|
|
306
|
+
});
|
|
307
|
+
} catch (e) {
|
|
308
|
+
resolve(null);
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
img.onerror = function () {
|
|
312
|
+
resolve(null);
|
|
313
|
+
};
|
|
314
|
+
img.src = src;
|
|
315
|
+
});
|
|
316
|
+
averageCache[src] = promise;
|
|
317
|
+
return promise.then(function (raw) {
|
|
318
|
+
return color.blendToBackground(raw, opts.background);
|
|
319
|
+
});
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
var root = typeof window !== 'undefined' ? window : {};
|
|
323
|
+
root.stellar = root.stellar || {};
|
|
324
|
+
root.stellar.color = color;
|
|
325
|
+
})();
|
package/source/js/main.js
CHANGED
|
@@ -353,7 +353,7 @@ const init = {
|
|
|
353
353
|
window.history.pushState(null, "", href);
|
|
354
354
|
}
|
|
355
355
|
}
|
|
356
|
-
sidebar.dismiss();
|
|
356
|
+
window.sidebar.dismiss();
|
|
357
357
|
});
|
|
358
358
|
},
|
|
359
359
|
wikiStart: () => {
|
|
@@ -451,7 +451,7 @@ const init = {
|
|
|
451
451
|
if (bar == null) {
|
|
452
452
|
return;
|
|
453
453
|
}
|
|
454
|
-
// getComputedStyle().top 自动兼容桌面 var(--gap-
|
|
454
|
+
// getComputedStyle().top 自动兼容桌面 var(--gap-page) 与移动端 8pt
|
|
455
455
|
const stickyTop = parseFloat(getComputedStyle(navbar).top) || 16;
|
|
456
456
|
states.push({
|
|
457
457
|
navbar: navbar,
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// 文字自适应颜色 DOM 插件
|
|
2
|
+
// 扫描 [data-text-adaptive] 元素,解析背景来源(--cover-url → --bg-url →
|
|
3
|
+
// background-image → background-color),调用 stellar.color 计算文字颜色,
|
|
4
|
+
// 写入内联 --text-banner 与 --text-banner-theme。
|
|
5
|
+
// 属性值:
|
|
6
|
+
// theme(默认) 背景图平均色 lighten/darken(两种变量同色)
|
|
7
|
+
// contrast 黑白对比(两种变量同色)
|
|
8
|
+
// split 大字(标题/headline)用 contrast,小字(caption/subtitle/面包屑等)用 theme
|
|
9
|
+
// 用户显式覆盖优先:元素已有内联 --text-banner 或内联 color 时跳过。
|
|
10
|
+
// 由 layout/_plugins/adaptive-text.ejs 在页面存在目标元素时懒加载。
|
|
11
|
+
|
|
12
|
+
function parseCssUrl(value) {
|
|
13
|
+
if (!value) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
var m = String(value).trim().match(/url\((['"]?)(.*?)\1\)/i);
|
|
17
|
+
return m ? m[2] : null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function resolveBackground(el) {
|
|
21
|
+
var style = window.getComputedStyle(el);
|
|
22
|
+
var cover = parseCssUrl(style.getPropertyValue('--cover-url'));
|
|
23
|
+
if (cover) {
|
|
24
|
+
return { type: 'image', url: cover };
|
|
25
|
+
}
|
|
26
|
+
var pinCover = parseCssUrl(style.getPropertyValue('--pin-cover-url'));
|
|
27
|
+
if (pinCover) {
|
|
28
|
+
return { type: 'image', url: pinCover };
|
|
29
|
+
}
|
|
30
|
+
var bg = parseCssUrl(style.getPropertyValue('--bg-url'));
|
|
31
|
+
if (bg) {
|
|
32
|
+
return { type: 'image', url: bg };
|
|
33
|
+
}
|
|
34
|
+
var bgImage = style.backgroundImage;
|
|
35
|
+
if (bgImage && bgImage !== 'none') {
|
|
36
|
+
var url = parseCssUrl(bgImage);
|
|
37
|
+
if (url && url !== 'none') {
|
|
38
|
+
return { type: 'image', url: url };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
var bgColor = style.backgroundColor;
|
|
42
|
+
if (bgColor && bgColor !== 'transparent' && !/rgba\(0,\s*0,\s*0,\s*0\)/.test(bgColor)) {
|
|
43
|
+
var rgb = stellar.color.parse(bgColor);
|
|
44
|
+
if (rgb) {
|
|
45
|
+
return { type: 'color', rgb: rgb };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 解析图片背后的实际背景色(用于透明图的平均色合成):
|
|
52
|
+
// 从元素自身沿祖先向上找第一个不透明 background-color(半透明视为继续向上),
|
|
53
|
+
// 最终兜底到 body 的 var(--background),仍无则白色。
|
|
54
|
+
function resolveBackdrop(el) {
|
|
55
|
+
var node = el;
|
|
56
|
+
while (node && node.nodeType === 1) {
|
|
57
|
+
var rgb = stellar.color.parse(window.getComputedStyle(node).backgroundColor);
|
|
58
|
+
if (rgb && rgb.a >= 1) {
|
|
59
|
+
return { r: rgb.r, g: rgb.g, b: rgb.b };
|
|
60
|
+
}
|
|
61
|
+
node = node.parentElement;
|
|
62
|
+
}
|
|
63
|
+
return { r: 255, g: 255, b: 255 };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 单样式模式:--text-banner 与 --text-banner-theme 取同一颜色,保持容器内文字一致
|
|
67
|
+
function setTextColor(el, styleName, rgb) {
|
|
68
|
+
var result = stellar.color.adaptiveTextColor(rgb, { style: styleName });
|
|
69
|
+
if (result) {
|
|
70
|
+
el.style.setProperty('--text-banner', result);
|
|
71
|
+
el.style.setProperty('--text-banner-theme', result);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// split 模式:大字用低饱和 theme(接近黑白,保留一点主色倾向),小字用完整 theme
|
|
76
|
+
function setSplitTextColors(el, rgb) {
|
|
77
|
+
// 大字更接近黑白:只保留 5% 饱和度(值越大越接近完整主题色,越小越接近黑白,0 为纯灰)
|
|
78
|
+
var large = stellar.color.adaptiveTextColor(rgb, { style: 'theme', saturationScale: 0.05 });
|
|
79
|
+
var theme = stellar.color.adaptiveTextColor(rgb, { style: 'theme' });
|
|
80
|
+
if (large) {
|
|
81
|
+
el.style.setProperty('--text-banner', large);
|
|
82
|
+
}
|
|
83
|
+
if (theme) {
|
|
84
|
+
el.style.setProperty('--text-banner-theme', theme);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function applyToElement(el) {
|
|
89
|
+
// 用户显式覆盖优先:首次处理时元素已有内联 --text-banner 或内联 color 则跳过
|
|
90
|
+
// (插件自身写入的变量不视为用户覆盖,主题切换重算时需重新应用)
|
|
91
|
+
var isFirst = !appliedElements.has(el);
|
|
92
|
+
if (isFirst && (el.style.getPropertyValue('--text-banner') || el.style.color)) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
var bg = resolveBackground(el);
|
|
96
|
+
if (!bg) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
var styleName = el.getAttribute('data-text-adaptive') || 'theme';
|
|
100
|
+
var apply = function (rgb) {
|
|
101
|
+
if (styleName === 'split') {
|
|
102
|
+
setSplitTextColors(el, rgb);
|
|
103
|
+
} else {
|
|
104
|
+
setTextColor(el, styleName, rgb);
|
|
105
|
+
}
|
|
106
|
+
appliedElements.add(el);
|
|
107
|
+
};
|
|
108
|
+
if (bg.type === 'color') {
|
|
109
|
+
apply(bg.rgb);
|
|
110
|
+
} else {
|
|
111
|
+
var backdrop = resolveBackdrop(el);
|
|
112
|
+
stellar.color.getAverageColor(bg.url, { background: backdrop }).then(function (rgb) {
|
|
113
|
+
if (rgb) {
|
|
114
|
+
apply(rgb);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
var adaptiveTextElements = [];
|
|
121
|
+
var appliedElements = new WeakSet();
|
|
122
|
+
|
|
123
|
+
function applyAdaptiveText(elements) {
|
|
124
|
+
if (!window.stellar || !window.stellar.color || !elements || elements.length === 0) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
adaptiveTextElements = Array.prototype.slice.call(elements);
|
|
128
|
+
for (var i = 0; i < elements.length; i++) {
|
|
129
|
+
applyToElement(elements[i]);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 主题明暗切换时重算:透明背景图的合成背景随 data-theme 变化,颜色需重新计算
|
|
134
|
+
if (window.MutationObserver && document.documentElement) {
|
|
135
|
+
new MutationObserver(function () {
|
|
136
|
+
applyAdaptiveText(adaptiveTextElements);
|
|
137
|
+
if (typeof window.refreshPinNavColor === 'function') {
|
|
138
|
+
window.refreshPinNavColor();
|
|
139
|
+
}
|
|
140
|
+
}).observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 供轮播箭头等场景复用:解析元素背后实际渲染背景色
|
|
144
|
+
window.resolveAdaptiveBackdrop = resolveBackdrop;
|
|
145
|
+
|
|
146
|
+
window.applyAdaptiveText = applyAdaptiveText;
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
}
|
|
89
89
|
if (item.comments != null) {
|
|
90
90
|
cell += '<a class="item comments last" href="' + item.html_url + '#issuecomment-new" target="_blank" rel="external nofollow noopener noreferrer">';
|
|
91
|
-
cell += '<span>' + ctx.icons['
|
|
91
|
+
cell += '<span>' + ctx.icons['default:tocomment'] + ' ' + (item.comments || 0) + '</span>';
|
|
92
92
|
cell += '</a>';
|
|
93
93
|
}
|
|
94
94
|
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
cell += '</a>';
|
|
41
41
|
cell += '</div>';
|
|
42
42
|
cell += '<a class="item comments last" href="' + item.url + '#issuecomment-new" target="_blank" rel="external nofollow noopener noreferrer">';
|
|
43
|
-
cell += '<span>' + ctx.icons['
|
|
43
|
+
cell += '<span>' + ctx.icons['default:tocomment'] + ' '
|
|
44
44
|
+ (item.comments_count || 0) + '</span>';
|
|
45
45
|
cell += '</a>';
|
|
46
46
|
cell += '<div class="item reaction attitudes">';
|
package/source/js/services.js
CHANGED
package/source/js/utils.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
(function () {
|
|
2
|
+
// 防重复执行:utils.js 可能被重复加载,二次执行直接跳过,避免重复声明报错。
|
|
3
|
+
if (window.__stellarUtilsLoaded) return;
|
|
4
|
+
window.__stellarUtilsLoaded = true;
|
|
5
|
+
|
|
2
6
|
function RunItem() {
|
|
3
7
|
this.list = []; // 存放回调函数
|
|
4
8
|
this.start = () => {
|
|
@@ -726,3 +730,12 @@
|
|
|
726
730
|
utils.dark = Object.assign(utils.dark, {
|
|
727
731
|
push: utils.dark.method.toggle.push,
|
|
728
732
|
});
|
|
733
|
+
|
|
734
|
+
// 暴露到 window:页尾插件片段经裸标识符 utils 即可使用
|
|
735
|
+
window.utils = utils;
|
|
736
|
+
|
|
737
|
+
// 补跑解析期注册的插件队列
|
|
738
|
+
if (window.stellar && typeof window.stellar._flushPlugins === 'function') {
|
|
739
|
+
window.stellar._flushPlugins();
|
|
740
|
+
}
|
|
741
|
+
})();
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
<%
|
|
2
|
-
function div() {
|
|
3
|
-
var el = '';
|
|
4
|
-
if ((page.banner == undefined || page.banner == false) && !theme.article.auto_banner) {
|
|
5
|
-
return el;
|
|
6
|
-
}
|
|
7
|
-
var url;
|
|
8
|
-
if (page.banner != undefined) {
|
|
9
|
-
if (page.banner.includes('/')) {
|
|
10
|
-
url = page.banner;
|
|
11
|
-
} else {
|
|
12
|
-
url = 'https://source.unsplash.com/2000x400/?' + page.banner;
|
|
13
|
-
}
|
|
14
|
-
} else {
|
|
15
|
-
// 自动以 tags 作为关键词搜索封面
|
|
16
|
-
if (page.tags) {
|
|
17
|
-
var params = '';
|
|
18
|
-
page.tags.reverse().forEach((tag, i) => {
|
|
19
|
-
if (i > 0) {
|
|
20
|
-
params += ',';
|
|
21
|
-
}
|
|
22
|
-
params += tag.name;
|
|
23
|
-
});
|
|
24
|
-
url = 'https://source.unsplash.com/2000x400/?' + params;
|
|
25
|
-
} else {
|
|
26
|
-
url = 'https://source.unsplash.com/random/2000x400';
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
el += '<div class="l_cover post' + scrollreveal(' ') + '">';
|
|
30
|
-
el += '<div class="cover">';
|
|
31
|
-
el += '<div class="lazy img bg" data-bg="' + escape_html(url) + '"></div>';
|
|
32
|
-
el += '</div>';
|
|
33
|
-
el += '</div>';
|
|
34
|
-
return el;
|
|
35
|
-
}
|
|
36
|
-
%>
|
|
37
|
-
<%- div() %>
|