hexo-theme-stellar 2.0.0-rc.5 → 2.0.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/CHANGELOG.md +66 -0
- package/README.md +8 -0
- package/README_EN.md +8 -0
- package/_config.yml +12 -1
- package/languages/en.yml +0 -2
- package/languages/zh-CN.yml +0 -2
- package/languages/zh-TW.yml +0 -2
- package/layout/_partial/cover/wiki_cover.ejs +9 -3
- package/layout/_partial/main/navbar/article_banner.ejs +9 -0
- package/layout/_partial/main/notebook/notebook_card.ejs +33 -13
- package/layout/_partial/primitives/navigation.ejs +3 -2
- package/layout/_partial/regions/widgets.ejs +1 -1
- package/layout/_partial/sidebar/menu.ejs +3 -0
- package/layout/notebooks.ejs +8 -6
- package/layout/settings.ejs +2 -4
- package/legal/THIRD-PARTY-NOTICES.md +2 -2
- package/package.json +1 -1
- package/scripts/events/lib/notebooks.js +5 -1
- package/scripts/lib/client-assets.js +1 -0
- package/scripts/lib/config-schema.js +2 -2
- package/scripts/lib/contribution-registry.js +14 -0
- package/scripts/lib/hero-effect-registry.js +50 -1
- package/scripts/lib/image-metadata.js +19 -4
- package/scripts/lib/internal-constants.js +1 -0
- package/scripts/lib/models/wiki.js +2 -0
- package/scripts/lib/regions.js +8 -1
- package/scripts/lib/source-config.js +6 -1
- package/scripts/schema/config-rules.js +3 -0
- package/scripts/schema/content-config-rules.js +4 -0
- package/scripts/schema/content-config-schema.js +2 -0
- package/scripts/schema/model-schema.js +4 -0
- package/source/css/_common/base.styl +1 -0
- package/source/css/_common/input.styl +12 -15
- package/source/css/_common/loading.styl +17 -0
- package/source/css/_components/pages/settings.styl +0 -1
- package/source/css/_components/partial/cover.styl +20 -0
- package/source/css/_components/sidebar/nav-area.styl +10 -0
- package/source/css/_components/sidebar/search.styl +65 -1
- package/source/css/_components/widgets/toc.styl +1 -1
- package/source/css/_components/wiki-card.styl +3 -1
- package/source/js/main.js +51 -4
- package/source/js/profile.js +1 -1
- package/source/js/runtime/extensions/hero-parallax.js +70 -0
- package/source/js/runtime/extensions/partial-navigation.js +18 -1
- package/source/js/runtime/extensions/settings.js +2 -3
- package/source/js/runtime/hero-effects/strands.js +302 -0
- package/source/js/runtime/image-color.js +4 -1
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/* Adapted from React Bits Strands (MIT, Copyright (c) 2026 David Haz).
|
|
2
|
+
* See legal/THIRD-PARTY-NOTICES.md. Native WebGL2 renderer for Stellar. */
|
|
3
|
+
|
|
4
|
+
const VERT = `#version 300 es
|
|
5
|
+
in vec2 position;
|
|
6
|
+
void main() {
|
|
7
|
+
gl_Position = vec4(position, 0.0, 1.0);
|
|
8
|
+
}
|
|
9
|
+
`;
|
|
10
|
+
|
|
11
|
+
const FRAG = `#version 300 es
|
|
12
|
+
precision highp float;
|
|
13
|
+
|
|
14
|
+
uniform float uTime;
|
|
15
|
+
uniform vec2 uResolution;
|
|
16
|
+
uniform vec3 uColors[8];
|
|
17
|
+
uniform int uColorCount;
|
|
18
|
+
uniform int uStrandCount;
|
|
19
|
+
uniform float uSpeed;
|
|
20
|
+
uniform float uAmplitude;
|
|
21
|
+
uniform float uWaviness;
|
|
22
|
+
uniform float uThickness;
|
|
23
|
+
uniform float uGlow;
|
|
24
|
+
uniform float uTaper;
|
|
25
|
+
uniform float uSpread;
|
|
26
|
+
uniform float uHueShift;
|
|
27
|
+
uniform float uIntensity;
|
|
28
|
+
uniform float uOpacity;
|
|
29
|
+
uniform float uScale;
|
|
30
|
+
uniform float uSaturation;
|
|
31
|
+
|
|
32
|
+
out vec4 fragColor;
|
|
33
|
+
|
|
34
|
+
const float PI = 3.14159265;
|
|
35
|
+
|
|
36
|
+
vec3 spectrum(float t) {
|
|
37
|
+
return 0.5 + 0.5 * cos(2.0 * PI * (t + vec3(0.00, 0.33, 0.67)));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
vec3 samplePalette(float t) {
|
|
41
|
+
t = fract(t);
|
|
42
|
+
float scaled = t * float(uColorCount);
|
|
43
|
+
int idx = int(floor(scaled));
|
|
44
|
+
float blend = fract(scaled);
|
|
45
|
+
int nextIdx = idx + 1;
|
|
46
|
+
if (nextIdx >= uColorCount) nextIdx = 0;
|
|
47
|
+
return mix(uColors[idx], uColors[nextIdx], blend);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
vec3 strandColor(float t) {
|
|
51
|
+
if (uColorCount > 0) return samplePalette(t);
|
|
52
|
+
return spectrum(t);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
void main() {
|
|
56
|
+
vec2 uv = (gl_FragCoord.xy - 0.5 * uResolution) / uResolution.y;
|
|
57
|
+
uv /= max(uScale, 0.0001);
|
|
58
|
+
|
|
59
|
+
float e = 0.06 + uIntensity * 0.94;
|
|
60
|
+
float env = pow(max(cos(uv.x * PI * 1.3), 0.0), uTaper);
|
|
61
|
+
|
|
62
|
+
vec3 col = vec3(0.0);
|
|
63
|
+
|
|
64
|
+
for (int i = 0; i < 12; i++) {
|
|
65
|
+
if (i >= uStrandCount) break;
|
|
66
|
+
|
|
67
|
+
float fi = float(i);
|
|
68
|
+
float ph = fi * 1.7 * uSpread;
|
|
69
|
+
float freq = (2.0 + fi * 0.35) * uWaviness;
|
|
70
|
+
float spd = 1.4 + fi * 1.2;
|
|
71
|
+
|
|
72
|
+
float tt = uTime * uSpeed;
|
|
73
|
+
float w = sin(uv.x * freq + tt * spd + ph) * 0.60
|
|
74
|
+
+ sin(uv.x * freq * 1.1 - tt * spd * 0.7 + ph * 1.7) * 0.40;
|
|
75
|
+
|
|
76
|
+
float amp = (0.1 + 0.02 * e) * env * uAmplitude;
|
|
77
|
+
float y = w * amp;
|
|
78
|
+
|
|
79
|
+
float d = abs(uv.y - y);
|
|
80
|
+
float thick = (0.001 + 0.05 * e) * (0.35 + env) * uThickness;
|
|
81
|
+
float g = thick / (d + thick * 0.45);
|
|
82
|
+
g = g * g;
|
|
83
|
+
|
|
84
|
+
float h = fi / float(uStrandCount) + uv.x * 0.30 + uTime * 0.04 + uHueShift;
|
|
85
|
+
col += strandColor(h) * g * env;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
col *= 0.45 + 0.7 * e;
|
|
89
|
+
col = 1.0 - exp(-col * uGlow);
|
|
90
|
+
|
|
91
|
+
float gray = dot(col, vec3(0.2126, 0.7152, 0.0722));
|
|
92
|
+
col = max(mix(vec3(gray), col, uSaturation), 0.0);
|
|
93
|
+
|
|
94
|
+
float lum = max(max(col.r, col.g), col.b);
|
|
95
|
+
float alpha = clamp(lum, 0.0, 1.0) * uOpacity;
|
|
96
|
+
|
|
97
|
+
fragColor = vec4(col * uOpacity, alpha);
|
|
98
|
+
}
|
|
99
|
+
`;
|
|
100
|
+
|
|
101
|
+
const GLASS_FRAG = `#version 300 es
|
|
102
|
+
precision highp float;
|
|
103
|
+
|
|
104
|
+
uniform sampler2D uScene;
|
|
105
|
+
uniform vec2 uResolution;
|
|
106
|
+
uniform float uRadius;
|
|
107
|
+
uniform float uRefraction;
|
|
108
|
+
uniform float uDispersion;
|
|
109
|
+
|
|
110
|
+
out vec4 fragColor;
|
|
111
|
+
|
|
112
|
+
vec2 toUv(vec2 p) {
|
|
113
|
+
return p * (uResolution.y / uResolution) + 0.5;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
void main() {
|
|
117
|
+
vec2 p = (gl_FragCoord.xy - 0.5 * uResolution) / uResolution.y;
|
|
118
|
+
float d = length(p);
|
|
119
|
+
float r = uRadius;
|
|
120
|
+
|
|
121
|
+
float edge = fwidth(d) * 1.5;
|
|
122
|
+
float mask = 1.0 - smoothstep(r - edge, r + edge, d);
|
|
123
|
+
if (mask <= 0.0) {
|
|
124
|
+
fragColor = vec4(0.0);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// sphere height: 0 at the rim, 1 at the center
|
|
129
|
+
float z = sqrt(max(r * r - d * d, 0.0)) / r;
|
|
130
|
+
float nd = d / r; // 0 at the center, 1 at the rim
|
|
131
|
+
|
|
132
|
+
// refraction is confined to a narrow band near the rim; the rest stays undistorted
|
|
133
|
+
vec2 dir = d > 0.0 ? p / d : vec2(0.0);
|
|
134
|
+
float lens = smoothstep(0.85, 1.0, nd) * pow(nd, 6.0);
|
|
135
|
+
vec2 offset = -dir * lens * uRefraction * 0.15;
|
|
136
|
+
vec2 disp = -dir * lens * uDispersion * 0.012;
|
|
137
|
+
|
|
138
|
+
vec3 light;
|
|
139
|
+
light.r = texture(uScene, toUv(p + offset - disp)).r;
|
|
140
|
+
light.g = texture(uScene, toUv(p + offset)).g;
|
|
141
|
+
light.b = texture(uScene, toUv(p + offset + disp)).b;
|
|
142
|
+
|
|
143
|
+
// neutral fresnel rim (no color tint so the glass stays clear)
|
|
144
|
+
float fres = pow(1.0 - z, 3.0);
|
|
145
|
+
vec3 rim = vec3(1.0) * fres * 0.18;
|
|
146
|
+
|
|
147
|
+
// specular highlight from the upper-left
|
|
148
|
+
vec2 lightDir = normalize(vec2(-0.55, 0.6));
|
|
149
|
+
float spec = pow(max(dot(p / max(r, 1e-4), lightDir), 0.0), 6.0);
|
|
150
|
+
spec *= (1.0 - smoothstep(r * 0.55, r, d));
|
|
151
|
+
|
|
152
|
+
vec3 emissive = light + rim + vec3(spec) * 0.4;
|
|
153
|
+
float emissiveA = clamp(max(max(emissive.r, emissive.g), emissive.b), 0.0, 1.0);
|
|
154
|
+
|
|
155
|
+
// almost clear glass body: only a faint neutral darkening, mostly near the rim
|
|
156
|
+
float bodyA = 0.05 + fres * 0.05;
|
|
157
|
+
|
|
158
|
+
// composite emissive light over the clear body (premultiplied)
|
|
159
|
+
float outA = emissiveA + bodyA * (1.0 - emissiveA);
|
|
160
|
+
vec3 outRGB = emissive;
|
|
161
|
+
|
|
162
|
+
outRGB *= mask;
|
|
163
|
+
outA *= mask;
|
|
164
|
+
|
|
165
|
+
fragColor = vec4(outRGB, outA);
|
|
166
|
+
}
|
|
167
|
+
`;
|
|
168
|
+
|
|
169
|
+
export function createRenderer(canvas, options, defaults) {
|
|
170
|
+
if (!canvas) return null;
|
|
171
|
+
let gl;
|
|
172
|
+
try {
|
|
173
|
+
gl = canvas.getContext('webgl2', { alpha: true, premultipliedAlpha: true, antialias: false });
|
|
174
|
+
} catch {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
if (!gl) return null;
|
|
178
|
+
const params = { ...defaults, ...options };
|
|
179
|
+
const programs = [];
|
|
180
|
+
const shaders = [];
|
|
181
|
+
let buffer;
|
|
182
|
+
let texture;
|
|
183
|
+
let framebuffer;
|
|
184
|
+
let destroyed = false;
|
|
185
|
+
|
|
186
|
+
function destroy() {
|
|
187
|
+
if (destroyed) return;
|
|
188
|
+
destroyed = true;
|
|
189
|
+
if (buffer) gl.deleteBuffer(buffer);
|
|
190
|
+
if (texture) gl.deleteTexture(texture);
|
|
191
|
+
if (framebuffer) gl.deleteFramebuffer(framebuffer);
|
|
192
|
+
programs.forEach(program => gl.deleteProgram(program));
|
|
193
|
+
shaders.forEach(shader => gl.deleteShader(shader));
|
|
194
|
+
gl.getExtension('WEBGL_lose_context')?.loseContext();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function programFor(fragment) {
|
|
198
|
+
const program = gl.createProgram();
|
|
199
|
+
programs.push(program);
|
|
200
|
+
for (const [type, source] of [[gl.VERTEX_SHADER, VERT], [gl.FRAGMENT_SHADER, fragment]]) {
|
|
201
|
+
const shader = gl.createShader(type);
|
|
202
|
+
shaders.push(shader);
|
|
203
|
+
gl.shaderSource(shader, source);
|
|
204
|
+
gl.compileShader(shader);
|
|
205
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(shader));
|
|
206
|
+
gl.attachShader(program, shader);
|
|
207
|
+
}
|
|
208
|
+
gl.linkProgram(program);
|
|
209
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) throw new Error(gl.getProgramInfoLog(program));
|
|
210
|
+
return program;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
const scene = programFor(FRAG);
|
|
215
|
+
const glass = params.glass ? programFor(GLASS_FRAG) : null;
|
|
216
|
+
buffer = gl.createBuffer();
|
|
217
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
218
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
|
|
219
|
+
function use(program) {
|
|
220
|
+
gl.useProgram(program);
|
|
221
|
+
const position = gl.getAttribLocation(program, 'position');
|
|
222
|
+
gl.enableVertexAttribArray(position);
|
|
223
|
+
gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0);
|
|
224
|
+
}
|
|
225
|
+
function scalar(program, name, value) {
|
|
226
|
+
gl.uniform1f(gl.getUniformLocation(program, name), value);
|
|
227
|
+
}
|
|
228
|
+
use(scene);
|
|
229
|
+
const palette = params.colors.slice(0, 8);
|
|
230
|
+
const colors = Array.from({ length: 8 }, (_, index) => {
|
|
231
|
+
const hex = (palette[index] || palette[palette.length - 1] || '#ffffff').replace(/^#/, '');
|
|
232
|
+
return [0, 2, 4].map(offset => parseInt(hex.slice(offset, offset + 2), 16) / 255);
|
|
233
|
+
}).flat();
|
|
234
|
+
gl.uniform3fv(gl.getUniformLocation(scene, 'uColors[0]'), colors);
|
|
235
|
+
gl.uniform1i(gl.getUniformLocation(scene, 'uColorCount'), palette.length);
|
|
236
|
+
gl.uniform1i(gl.getUniformLocation(scene, 'uStrandCount'), Math.min(12, Math.max(1, Math.round(params.count))));
|
|
237
|
+
for (const name of ['speed', 'amplitude', 'waviness', 'thickness', 'glow', 'taper', 'spread', 'hueShift', 'intensity', 'opacity', 'scale', 'saturation']) {
|
|
238
|
+
let value = params[name];
|
|
239
|
+
if (name !== 'speed' && name !== 'hueShift') value = Math.max(0, value);
|
|
240
|
+
if (name === 'thickness' || name === 'scale') value = Math.max(0.0001, value);
|
|
241
|
+
if (name === 'opacity') value = Math.min(1, value);
|
|
242
|
+
scalar(scene, `u${name[0].toUpperCase()}${name.slice(1)}`, value);
|
|
243
|
+
}
|
|
244
|
+
const timeLocation = gl.getUniformLocation(scene, 'uTime');
|
|
245
|
+
if (glass) {
|
|
246
|
+
use(glass);
|
|
247
|
+
scalar(glass, 'uRadius', Math.max(0.0001, 0.46 * params.glassSize));
|
|
248
|
+
scalar(glass, 'uRefraction', params.refraction);
|
|
249
|
+
scalar(glass, 'uDispersion', params.dispersion);
|
|
250
|
+
gl.uniform1i(gl.getUniformLocation(glass, 'uScene'), 0);
|
|
251
|
+
texture = gl.createTexture();
|
|
252
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
253
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
254
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
255
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
256
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
257
|
+
framebuffer = gl.createFramebuffer();
|
|
258
|
+
}
|
|
259
|
+
let width = 0;
|
|
260
|
+
let height = 0;
|
|
261
|
+
return Object.freeze({
|
|
262
|
+
pointer: false,
|
|
263
|
+
resize(rect, devicePixelRatio) {
|
|
264
|
+
const dpr = Math.min(devicePixelRatio || 1, 2);
|
|
265
|
+
const nextWidth = Math.max(1, Math.round(rect.width * dpr));
|
|
266
|
+
const nextHeight = Math.max(1, Math.round(rect.height * dpr));
|
|
267
|
+
if (width === nextWidth && height === nextHeight) return;
|
|
268
|
+
width = canvas.width = nextWidth;
|
|
269
|
+
height = canvas.height = nextHeight;
|
|
270
|
+
gl.viewport(0, 0, width, height);
|
|
271
|
+
for (const program of [scene, glass].filter(Boolean)) {
|
|
272
|
+
use(program);
|
|
273
|
+
gl.uniform2f(gl.getUniformLocation(program, 'uResolution'), width, height);
|
|
274
|
+
}
|
|
275
|
+
if (glass) {
|
|
276
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
277
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
278
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
|
279
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
|
|
280
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
render(time) {
|
|
284
|
+
if (destroyed) return;
|
|
285
|
+
use(scene);
|
|
286
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, glass ? framebuffer : null);
|
|
287
|
+
gl.uniform1f(timeLocation, time * 0.001);
|
|
288
|
+
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
289
|
+
if (glass) {
|
|
290
|
+
use(glass);
|
|
291
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
292
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
293
|
+
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
destroy
|
|
297
|
+
});
|
|
298
|
+
} catch {
|
|
299
|
+
destroy();
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
@@ -195,7 +195,10 @@ export function readImageColor(image, options = {}) {
|
|
|
195
195
|
function onLoad() {
|
|
196
196
|
if (options.signal?.aborted || source(image) !== url) return finish(null);
|
|
197
197
|
const displayed = normalize(image.currentSrc || image.getAttribute('src'), image);
|
|
198
|
-
|
|
198
|
+
// 懒加载占位图(src 仍是 1×1 data URI):显示像素不可用,直接 return 会让本 Promise 永久挂起
|
|
199
|
+
// (懒加载运行时没执行时,真图永远不会被换进来),自适应文字与 --wiki-border-color 就全都缺失。
|
|
200
|
+
// 改为按 URL 单独取色,保证颜色不依赖懒加载是否跑通。
|
|
201
|
+
if (displayed !== url) return finish(waitForSample(sampleWithCors(image, url, size, key), options.signal));
|
|
199
202
|
if (!image.naturalWidth || !image.naturalHeight) return finish(null);
|
|
200
203
|
const existing = read(key);
|
|
201
204
|
const raw = existing || sample(image, size);
|