@xingwangzhe/stalux 1.41.5 → 1.43.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/README.md +19 -5
- package/package.json +12 -3
- package/src/components/stalux/archives/archivesList.astro +0 -2
- package/src/components/stalux/archives/toArchive.astro +1 -5
- package/src/components/stalux/common/Typewriter.astro +1 -7
- package/src/components/stalux/common/author.astro +1 -6
- package/src/components/stalux/common/date.astro +1 -11
- package/src/components/stalux/layout/PageHeading.astro +2 -11
- package/src/components/stalux/layout/footer.astro +1 -6
- package/src/components/stalux/layout/head.astro +9 -2
- package/src/components/stalux/links/socialMedia.astro +1 -10
- package/src/components/stalux/posts/ArticleSummary.astro +2 -8
- package/src/components/stalux/posts/PostTaxonomy.astro +3 -14
- package/src/components/stalux/posts/postCC.astro +2 -2
- package/src/components/stalux/posts/postCard.astro +1 -5
- package/src/components/stalux/posts/postContent.astro +4 -23
- package/src/components/stalux/posts/postLeft.astro +4 -22
- package/src/components/stalux/posts/postRight.astro +5 -27
- package/src/components/stalux/posts/postToc.astro +3 -10
- package/src/components/stalux/posts/randomPost.astro +0 -1
- package/src/components/stalux/tags/tagHeader.astro +1 -5
- package/src/env.d.ts +5 -0
- package/src/index.ts +41 -2
- package/src/internal/social-card-data.ts +34 -0
- package/src/internal/social-card-renderer.ts +307 -0
- package/src/internal/style-names.generated.ts +59 -69
- package/src/layouts/PostLayout.astro +1 -5
- package/src/layouts/Stalux.astro +6 -19
- package/src/pages/index.astro +1 -5
- package/src/pages/og/[image].[format].ts +47 -0
- package/src/pages/posts/[post].astro +8 -0
- package/src/pages/posts/[post].md.ts +1 -1
- package/src/schemas/collections.ts +9 -0
- package/src/scripts/navigation-client.ts +13 -0
- package/src/scripts/navigation-scripts.ts +191 -0
- package/src/scripts/route-motion.ts +78 -11
- package/src/scripts/swup-router.ts +366 -0
- package/src/scripts/tags-cloud.ts +1 -1
- package/src/scripts/view-transitions.ts +3 -233
- package/src/scripts/word-list.ts +9 -3
- package/src/styles/generated.css +1 -1
- package/src/styles/motion.css +2 -211
- package/src/utils/post-markdown.ts +2 -0
- package/src/utils/route-catalog.ts +1 -0
- package/src/utils/seo-document.ts +21 -4
- package/src/utils/social-image.ts +60 -0
- package/src/scripts/archive-reveal.ts +0 -76
package/src/index.ts
CHANGED
|
@@ -120,6 +120,8 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
120
120
|
...options,
|
|
121
121
|
};
|
|
122
122
|
|
|
123
|
+
const socialCardPaths = { publicDir: "", cacheDir: "", base: "/", fontPath: "" };
|
|
124
|
+
|
|
123
125
|
let pageFontData: { fontBuffer: Buffer; outputDir: string; cacheDir: string } | undefined;
|
|
124
126
|
|
|
125
127
|
const coreIntegration: AstroIntegration = {
|
|
@@ -161,17 +163,48 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
161
163
|
config.build.inlineStylesheets === "always" ? "always" : "never",
|
|
162
164
|
},
|
|
163
165
|
vite: {
|
|
164
|
-
optimizeDeps: {
|
|
166
|
+
optimizeDeps: {
|
|
167
|
+
exclude: ["@mcp-b/webmcp-polyfill"],
|
|
168
|
+
// Discover lazy page features before the first visit:
|
|
169
|
+
// Vite re-optimization would otherwise reload the
|
|
170
|
+
// entire document midway through a Swup navigation.
|
|
171
|
+
include: [
|
|
172
|
+
"swup",
|
|
173
|
+
"@swup/a11y-plugin",
|
|
174
|
+
"@swup/head-plugin",
|
|
175
|
+
"@swup/js-plugin",
|
|
176
|
+
"@swup/preload-plugin",
|
|
177
|
+
"@swup/scroll-plugin",
|
|
178
|
+
"@microsoft/clarity",
|
|
179
|
+
"@pagefind/component-ui",
|
|
180
|
+
"@waline/client",
|
|
181
|
+
"@xingwangzhe/tags-cloud",
|
|
182
|
+
"photoswipe",
|
|
183
|
+
"photoswipe/lightbox",
|
|
184
|
+
],
|
|
185
|
+
},
|
|
165
186
|
css: { lightningcss: { targets: modernCssTargets } },
|
|
166
187
|
build: {
|
|
167
188
|
target: config.vite.build?.target ?? "esnext",
|
|
168
189
|
cssMinify: "lightningcss",
|
|
169
|
-
sourcemap
|
|
190
|
+
// 默认不产出 sourcemap;消费方显式开启时(如 SONDA=1 做体积分析)尊重其设置
|
|
191
|
+
sourcemap: config.vite.build?.sourcemap ?? false,
|
|
170
192
|
},
|
|
171
193
|
resolve: {
|
|
172
194
|
alias: createViteAliases(srcDir),
|
|
173
195
|
},
|
|
174
196
|
plugins: [
|
|
197
|
+
{
|
|
198
|
+
name: "stalux:social-card-paths",
|
|
199
|
+
resolveId: (id) =>
|
|
200
|
+
id === "virtual:stalux-social-card-paths"
|
|
201
|
+
? `\0${id}`
|
|
202
|
+
: undefined,
|
|
203
|
+
load: (id) =>
|
|
204
|
+
id === "\0virtual:stalux-social-card-paths"
|
|
205
|
+
? `export default ${JSON.stringify(socialCardPaths)}`
|
|
206
|
+
: undefined,
|
|
207
|
+
},
|
|
175
208
|
searchStylePlugin(command === "dev", config.base),
|
|
176
209
|
webMcpMutationsPlugin(),
|
|
177
210
|
...(command === "build"
|
|
@@ -339,6 +372,12 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
339
372
|
"astro:config:done": ({ config, logger }) => {
|
|
340
373
|
const srcDir = fileURLToPath(new URL(".", import.meta.url));
|
|
341
374
|
const publicDir = fileURLToPath(config.publicDir);
|
|
375
|
+
Object.assign(socialCardPaths, {
|
|
376
|
+
publicDir,
|
|
377
|
+
cacheDir: fileURLToPath(new URL("stalux-social-cards/", config.cacheDir)),
|
|
378
|
+
base: config.base,
|
|
379
|
+
fontPath: path.join(srcDir, "assets/fonts/LXGWWenKai-Regular.ttf"),
|
|
380
|
+
});
|
|
342
381
|
const assetsLogger = logger.fork("stalux/assets");
|
|
343
382
|
if (pageFontData) {
|
|
344
383
|
pageFontData.outputDir = path.join(
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { AuthorData, SiteData } from "../schemas/config";
|
|
2
|
+
import { formatInTimeZone, parseDate } from "../utils/dayjs";
|
|
3
|
+
import type { SocialCardData } from "../utils/social-image";
|
|
4
|
+
|
|
5
|
+
interface PostData {
|
|
6
|
+
title: string;
|
|
7
|
+
desc: string;
|
|
8
|
+
date: string;
|
|
9
|
+
cover?: string;
|
|
10
|
+
categories?: string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function createSocialCardData(
|
|
14
|
+
post: PostData,
|
|
15
|
+
site: SiteData,
|
|
16
|
+
author: AuthorData,
|
|
17
|
+
): SocialCardData {
|
|
18
|
+
return {
|
|
19
|
+
title: post.title,
|
|
20
|
+
description: post.desc,
|
|
21
|
+
siteTitle: site.title,
|
|
22
|
+
author: author.name,
|
|
23
|
+
host: new URL(site.canonical ?? site.url).host,
|
|
24
|
+
lang: site.lang || "zh-CN",
|
|
25
|
+
accent: site.accentColor || "#EAB308",
|
|
26
|
+
date: formatInTimeZone(
|
|
27
|
+
parseDate(post.date, site.timezone || "Asia/Shanghai"),
|
|
28
|
+
site.timezone || "Asia/Shanghai",
|
|
29
|
+
"yyyy-MM-dd",
|
|
30
|
+
),
|
|
31
|
+
category: post.categories?.[0] ?? "",
|
|
32
|
+
...(post.cover?.trim() ? { cover: post.cover.trim() } : {}),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { type Node, Renderer } from "@takumi-rs/core";
|
|
5
|
+
import { imageSize } from "image-size";
|
|
6
|
+
import type { SocialCardData } from "../utils/social-image";
|
|
7
|
+
|
|
8
|
+
export interface SocialCardPaths {
|
|
9
|
+
publicDir: string;
|
|
10
|
+
cacheDir: string;
|
|
11
|
+
base: string;
|
|
12
|
+
fontPath: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const MAX_COVER_BYTES = 8 * 1024 * 1024;
|
|
16
|
+
let rendererPromise: Promise<Renderer> | undefined;
|
|
17
|
+
function getRenderer(fontPath: string): Promise<Renderer> {
|
|
18
|
+
rendererPromise ??= (async () => {
|
|
19
|
+
const renderer = new Renderer({ cacheMaxBytes: 32 * 1024 * 1024 });
|
|
20
|
+
await renderer.registerFont({ name: "Stalux Card", data: await readFile(fontPath) });
|
|
21
|
+
return renderer;
|
|
22
|
+
})();
|
|
23
|
+
return rendererPromise;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Resolve only assets inside the final public directory, including encoded Chinese paths. */
|
|
27
|
+
export function localCoverPath(cover: string, paths: SocialCardPaths): string | undefined {
|
|
28
|
+
if (!cover.startsWith("/") || cover.startsWith("//")) return undefined;
|
|
29
|
+
try {
|
|
30
|
+
let pathname = decodeURIComponent(cover.split(/[?#]/u)[0] ?? "");
|
|
31
|
+
const base = paths.base.replace(/\/$/u, "");
|
|
32
|
+
if (base && pathname.startsWith(`${base}/`)) pathname = pathname.slice(base.length);
|
|
33
|
+
const target = path.resolve(paths.publicDir, `.${pathname}`);
|
|
34
|
+
if (!target.startsWith(`${path.resolve(paths.publicDir)}${path.sep}`)) return undefined;
|
|
35
|
+
return target;
|
|
36
|
+
} catch {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const coverRequests = new Map<string, Promise<Buffer>>();
|
|
42
|
+
|
|
43
|
+
async function loadCover(cover: string, paths: SocialCardPaths): Promise<Buffer> {
|
|
44
|
+
const key = `${paths.publicDir}|${cover}`;
|
|
45
|
+
let pending = coverRequests.get(key);
|
|
46
|
+
if (!pending) {
|
|
47
|
+
pending = loadCoverUncached(cover, paths);
|
|
48
|
+
coverRequests.set(key, pending);
|
|
49
|
+
// Bound memory while sharing requests and failures between WebP and PNG renders.
|
|
50
|
+
if (coverRequests.size > 8) coverRequests.delete(coverRequests.keys().next().value ?? "");
|
|
51
|
+
}
|
|
52
|
+
return pending;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function loadCoverUncached(cover: string, paths: SocialCardPaths): Promise<Buffer> {
|
|
56
|
+
const local = localCoverPath(cover, paths);
|
|
57
|
+
let bytes: Buffer;
|
|
58
|
+
if (local) {
|
|
59
|
+
if ((await stat(local)).size > MAX_COVER_BYTES) throw new Error("cover exceeds 8 MiB");
|
|
60
|
+
bytes = await readFile(local);
|
|
61
|
+
} else {
|
|
62
|
+
const url = new URL(cover.startsWith("//") ? `https:${cover}` : cover);
|
|
63
|
+
if (!["https:", "http:"].includes(url.protocol)) throw new Error("invalid cover URL");
|
|
64
|
+
const remoteCache = path.join(
|
|
65
|
+
paths.cacheDir,
|
|
66
|
+
"covers",
|
|
67
|
+
createHash("sha256").update(url.href).digest("hex"),
|
|
68
|
+
);
|
|
69
|
+
const cachedStat = await stat(remoteCache).catch(() => undefined);
|
|
70
|
+
if (cachedStat && Date.now() - cachedStat.mtimeMs < 86_400_000)
|
|
71
|
+
return readFile(remoteCache);
|
|
72
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(8000) });
|
|
73
|
+
if (!response.ok || !response.headers.get("content-type")?.startsWith("image/"))
|
|
74
|
+
throw new Error(`cover returned HTTP ${response.status} or non-image content`);
|
|
75
|
+
const chunks: Uint8Array[] = [];
|
|
76
|
+
let size = 0;
|
|
77
|
+
if (!response.body) throw new Error("empty cover");
|
|
78
|
+
for await (const chunk of response.body) {
|
|
79
|
+
size += chunk.length;
|
|
80
|
+
if (size > MAX_COVER_BYTES) throw new Error("cover exceeds 8 MiB");
|
|
81
|
+
chunks.push(chunk);
|
|
82
|
+
}
|
|
83
|
+
bytes = Buffer.concat(chunks);
|
|
84
|
+
const dimensions = imageSize(bytes);
|
|
85
|
+
if (
|
|
86
|
+
!dimensions.width ||
|
|
87
|
+
!dimensions.height ||
|
|
88
|
+
dimensions.width * dimensions.height > 32_000_000
|
|
89
|
+
)
|
|
90
|
+
throw new Error("invalid cover dimensions");
|
|
91
|
+
await mkdir(path.dirname(remoteCache), { recursive: true });
|
|
92
|
+
await writeFile(remoteCache, bytes);
|
|
93
|
+
}
|
|
94
|
+
const dimensions = imageSize(bytes);
|
|
95
|
+
if (
|
|
96
|
+
!dimensions.width ||
|
|
97
|
+
!dimensions.height ||
|
|
98
|
+
dimensions.width * dimensions.height > 32_000_000
|
|
99
|
+
)
|
|
100
|
+
throw new Error("invalid or oversized cover dimensions");
|
|
101
|
+
return bytes;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function text(value: string, style: Node["style"]): Node {
|
|
105
|
+
return { type: "text", text: value, style };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** A fixed editorial layout: site identity, readable title, optional artwork, quiet footer. */
|
|
109
|
+
export function socialCardTree(
|
|
110
|
+
data: SocialCardData,
|
|
111
|
+
hasCover: boolean,
|
|
112
|
+
titleSize = 60,
|
|
113
|
+
title = data.title,
|
|
114
|
+
): Node {
|
|
115
|
+
const titleWidth = hasCover ? 650 : 1056;
|
|
116
|
+
const accent = /^#[\da-f]{6}$/iu.test(data.accent) ? data.accent : "#EAB308";
|
|
117
|
+
const children: Node[] = [
|
|
118
|
+
{
|
|
119
|
+
type: "container",
|
|
120
|
+
style: {
|
|
121
|
+
position: "absolute",
|
|
122
|
+
right: -120,
|
|
123
|
+
top: -200,
|
|
124
|
+
width: 650,
|
|
125
|
+
height: 650,
|
|
126
|
+
borderRadius: "50%",
|
|
127
|
+
border: `1px solid ${accent}35`,
|
|
128
|
+
background: `radial-gradient(circle, ${accent}18, transparent 70%)`,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
type: "container",
|
|
133
|
+
style: {
|
|
134
|
+
position: "absolute",
|
|
135
|
+
left: 0,
|
|
136
|
+
top: 0,
|
|
137
|
+
width: 1200,
|
|
138
|
+
height: 6,
|
|
139
|
+
backgroundColor: accent,
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
text(data.siteTitle, {
|
|
143
|
+
position: "absolute",
|
|
144
|
+
left: 72,
|
|
145
|
+
top: 48,
|
|
146
|
+
width: 820,
|
|
147
|
+
height: 46,
|
|
148
|
+
fontSize: 28,
|
|
149
|
+
color: accent,
|
|
150
|
+
overflow: "hidden",
|
|
151
|
+
}),
|
|
152
|
+
text(data.category || "ARTICLE", {
|
|
153
|
+
position: "absolute",
|
|
154
|
+
right: 72,
|
|
155
|
+
top: 54,
|
|
156
|
+
width: 240,
|
|
157
|
+
height: 36,
|
|
158
|
+
textAlign: "right",
|
|
159
|
+
fontSize: 21,
|
|
160
|
+
color: "#a9b5c6",
|
|
161
|
+
overflow: "hidden",
|
|
162
|
+
}),
|
|
163
|
+
text(title, {
|
|
164
|
+
position: "absolute",
|
|
165
|
+
left: 72,
|
|
166
|
+
top: 142,
|
|
167
|
+
width: titleWidth,
|
|
168
|
+
height: 260,
|
|
169
|
+
fontSize: titleSize,
|
|
170
|
+
lineHeight: 1.24,
|
|
171
|
+
color: "#f5f7fb",
|
|
172
|
+
overflow: "hidden",
|
|
173
|
+
overflowWrap: "anywhere",
|
|
174
|
+
}),
|
|
175
|
+
text([...data.description.replace(/\s+/gu, " ")].slice(0, hasCover ? 65 : 100).join(""), {
|
|
176
|
+
position: "absolute",
|
|
177
|
+
left: 72,
|
|
178
|
+
top: 421,
|
|
179
|
+
width: titleWidth,
|
|
180
|
+
height: 76,
|
|
181
|
+
fontSize: 25,
|
|
182
|
+
lineHeight: 1.45,
|
|
183
|
+
color: "#a9b5c6",
|
|
184
|
+
overflow: "hidden",
|
|
185
|
+
}),
|
|
186
|
+
{
|
|
187
|
+
type: "container",
|
|
188
|
+
style: {
|
|
189
|
+
position: "absolute",
|
|
190
|
+
left: 72,
|
|
191
|
+
top: 535,
|
|
192
|
+
width: 1056,
|
|
193
|
+
height: 1,
|
|
194
|
+
backgroundColor: "#ffffff22",
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
text(data.host, { position: "absolute", left: 72, top: 560, fontSize: 23, color: accent }),
|
|
198
|
+
text(`${data.author} / ${data.date}`, {
|
|
199
|
+
position: "absolute",
|
|
200
|
+
right: 72,
|
|
201
|
+
top: 560,
|
|
202
|
+
width: 650,
|
|
203
|
+
textAlign: "right",
|
|
204
|
+
fontSize: 23,
|
|
205
|
+
color: "#b9c4d3",
|
|
206
|
+
}),
|
|
207
|
+
];
|
|
208
|
+
if (hasCover)
|
|
209
|
+
children.push({
|
|
210
|
+
type: "image",
|
|
211
|
+
src: "stalux-cover",
|
|
212
|
+
style: {
|
|
213
|
+
position: "absolute",
|
|
214
|
+
right: 72,
|
|
215
|
+
top: 142,
|
|
216
|
+
width: 350,
|
|
217
|
+
height: 354,
|
|
218
|
+
objectFit: "cover",
|
|
219
|
+
borderRadius: 20,
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
return {
|
|
223
|
+
type: "container",
|
|
224
|
+
lang: data.lang,
|
|
225
|
+
style: {
|
|
226
|
+
width: 1200,
|
|
227
|
+
height: 630,
|
|
228
|
+
position: "relative",
|
|
229
|
+
backgroundColor: "#101722",
|
|
230
|
+
fontFamily: "Stalux Card",
|
|
231
|
+
overflow: "hidden",
|
|
232
|
+
},
|
|
233
|
+
children,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export async function renderSocialCard(
|
|
238
|
+
data: SocialCardData,
|
|
239
|
+
paths: SocialCardPaths,
|
|
240
|
+
warn: (message: string) => void,
|
|
241
|
+
format: "webp" | "png" = "webp",
|
|
242
|
+
): Promise<Buffer> {
|
|
243
|
+
let cover: Buffer | undefined;
|
|
244
|
+
if (data.cover) {
|
|
245
|
+
try {
|
|
246
|
+
cover = await loadCover(data.cover, paths);
|
|
247
|
+
} catch {
|
|
248
|
+
warn(`分享封面不可用,已回退标题卡片:${data.title}`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const font = await stat(paths.fontPath);
|
|
252
|
+
const key = createHash("sha256")
|
|
253
|
+
.update(JSON.stringify(["v2", format, data, font.size, font.mtimeMs]))
|
|
254
|
+
.update(cover ?? "")
|
|
255
|
+
.digest("hex");
|
|
256
|
+
const cacheFile = path.join(paths.cacheDir, `${key}.${format}`);
|
|
257
|
+
const hit = await readFile(cacheFile).catch(() => undefined);
|
|
258
|
+
if (hit) return hit;
|
|
259
|
+
const renderer = await getRenderer(paths.fontPath);
|
|
260
|
+
const layout = async (withCover: boolean) => {
|
|
261
|
+
const width = withCover ? 650 : 1056;
|
|
262
|
+
let title = [...data.title].slice(0, 150).join("");
|
|
263
|
+
let size = 66;
|
|
264
|
+
while (true) {
|
|
265
|
+
const measured = await renderer.measure(
|
|
266
|
+
{
|
|
267
|
+
type: "text",
|
|
268
|
+
text: title,
|
|
269
|
+
style: {
|
|
270
|
+
width,
|
|
271
|
+
fontSize: size,
|
|
272
|
+
lineHeight: 1.24,
|
|
273
|
+
fontFamily: "Stalux Card",
|
|
274
|
+
overflowWrap: "anywhere",
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
{ width },
|
|
278
|
+
);
|
|
279
|
+
if (measured.height <= 250) break;
|
|
280
|
+
if (size > 30) size -= 2;
|
|
281
|
+
else title = `${[...title].slice(0, -3).join("")}…`;
|
|
282
|
+
}
|
|
283
|
+
return socialCardTree(data, withCover, size, title);
|
|
284
|
+
};
|
|
285
|
+
let bytes: Buffer;
|
|
286
|
+
try {
|
|
287
|
+
bytes = await renderer.render(await layout(Boolean(cover)), {
|
|
288
|
+
width: 1200,
|
|
289
|
+
height: 630,
|
|
290
|
+
format,
|
|
291
|
+
quality: 86,
|
|
292
|
+
images: cover ? [{ src: "stalux-cover", data: cover }] : [],
|
|
293
|
+
});
|
|
294
|
+
} catch (error) {
|
|
295
|
+
if (!cover) throw error;
|
|
296
|
+
warn(`分享封面解码失败,已回退标题卡片:${data.title}`);
|
|
297
|
+
bytes = await renderer.render(await layout(false), {
|
|
298
|
+
width: 1200,
|
|
299
|
+
height: 630,
|
|
300
|
+
format,
|
|
301
|
+
quality: 86,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
await mkdir(paths.cacheDir, { recursive: true });
|
|
305
|
+
await writeFile(cacheFile, bytes);
|
|
306
|
+
return bytes;
|
|
307
|
+
}
|
|
@@ -290,80 +290,70 @@ export const styleNames: { classes: Record<string, string>; variables: Record<st
|
|
|
290
290
|
"--stalux-site-header-height": "--s1q",
|
|
291
291
|
"--stalux-site-header-min-height": "--s1r",
|
|
292
292
|
"--stalux-text-shadow": "--s1s",
|
|
293
|
-
"--
|
|
294
|
-
"--
|
|
295
|
-
"--
|
|
296
|
-
"--
|
|
297
|
-
"--
|
|
298
|
-
"--
|
|
299
|
-
"--
|
|
300
|
-
"--
|
|
301
|
-
"--
|
|
302
|
-
"--
|
|
303
|
-
"--
|
|
304
|
-
"--
|
|
305
|
-
"--
|
|
306
|
-
"--
|
|
307
|
-
"--
|
|
308
|
-
"--
|
|
309
|
-
"--
|
|
310
|
-
"--
|
|
311
|
-
"--
|
|
312
|
-
"--
|
|
313
|
-
"--
|
|
314
|
-
"--un-
|
|
315
|
-
"--un-
|
|
316
|
-
"--un-
|
|
317
|
-
"--
|
|
318
|
-
"--
|
|
319
|
-
"--
|
|
320
|
-
"--
|
|
321
|
-
"--
|
|
322
|
-
"--
|
|
323
|
-
"--
|
|
324
|
-
"--
|
|
325
|
-
"--
|
|
326
|
-
"--
|
|
327
|
-
"--
|
|
328
|
-
"--white-
|
|
329
|
-
"--white-
|
|
330
|
-
"--white-
|
|
331
|
-
"--white-
|
|
332
|
-
"--white-
|
|
333
|
-
"--white-
|
|
334
|
-
"--white-60p": "--s2y",
|
|
335
|
-
"--white-70p": "--s2z",
|
|
336
|
-
"--white-75p": "--s30",
|
|
337
|
-
"--white-78p": "--s31",
|
|
338
|
-
"--white-80p": "--s32",
|
|
339
|
-
"--white-85p": "--s33",
|
|
340
|
-
"--white-90p": "--s34",
|
|
341
|
-
"--white-95p": "--s35",
|
|
342
|
-
"--white-full": "--s36"
|
|
293
|
+
"--surface-glass": "--s1t",
|
|
294
|
+
"--surface-glass-shadow": "--s1u",
|
|
295
|
+
"--tag-bg": "--s1v",
|
|
296
|
+
"--tag-bg-hover": "--s1w",
|
|
297
|
+
"--tl-line": "--s1x",
|
|
298
|
+
"--tl-pad": "--s1y",
|
|
299
|
+
"--transition-fast": "--s1z",
|
|
300
|
+
"--transition-medium": "--s20",
|
|
301
|
+
"--transition-normal": "--s21",
|
|
302
|
+
"--transition-slow": "--s22",
|
|
303
|
+
"--transition-slower": "--s23",
|
|
304
|
+
"--transition-slowest": "--s24",
|
|
305
|
+
"--un-rotate": "--s25",
|
|
306
|
+
"--un-rotate-x": "--s26",
|
|
307
|
+
"--un-rotate-y": "--s27",
|
|
308
|
+
"--un-rotate-z": "--s28",
|
|
309
|
+
"--un-scale-x": "--s29",
|
|
310
|
+
"--un-scale-y": "--s2a",
|
|
311
|
+
"--un-scale-z": "--s2b",
|
|
312
|
+
"--un-skew-x": "--s2c",
|
|
313
|
+
"--un-skew-y": "--s2d",
|
|
314
|
+
"--un-translate-x": "--s2e",
|
|
315
|
+
"--un-translate-y": "--s2f",
|
|
316
|
+
"--un-translate-z": "--s2g",
|
|
317
|
+
"--updated-bg": "--s2h",
|
|
318
|
+
"--updated-border": "--s2i",
|
|
319
|
+
"--white-05p": "--s2j",
|
|
320
|
+
"--white-10p": "--s2k",
|
|
321
|
+
"--white-15p": "--s2l",
|
|
322
|
+
"--white-20p": "--s2m",
|
|
323
|
+
"--white-30p": "--s2n",
|
|
324
|
+
"--white-50p": "--s2o",
|
|
325
|
+
"--white-60p": "--s2p",
|
|
326
|
+
"--white-70p": "--s2q",
|
|
327
|
+
"--white-75p": "--s2r",
|
|
328
|
+
"--white-78p": "--s2s",
|
|
329
|
+
"--white-80p": "--s2t",
|
|
330
|
+
"--white-85p": "--s2u",
|
|
331
|
+
"--white-90p": "--s2v",
|
|
332
|
+
"--white-95p": "--s2w",
|
|
333
|
+
"--white-full": "--s2x"
|
|
343
334
|
},
|
|
344
335
|
"animations": {
|
|
345
336
|
"fadeInUpComment": "k0",
|
|
346
337
|
"spin": "k1",
|
|
347
338
|
"stalux-article-enter": "k2",
|
|
348
339
|
"stalux-article-enter-back": "k3",
|
|
349
|
-
"stalux-
|
|
350
|
-
"stalux-
|
|
351
|
-
"stalux-
|
|
352
|
-
"stalux-page-enter": "k7",
|
|
353
|
-
"stalux-page-
|
|
354
|
-
"stalux-
|
|
355
|
-
"stalux-
|
|
356
|
-
"stalux-sidebar-left-back-
|
|
357
|
-
"stalux-sidebar-left-
|
|
358
|
-
"stalux-sidebar-left-
|
|
359
|
-
"stalux-sidebar-
|
|
360
|
-
"stalux-sidebar-right-back-
|
|
361
|
-
"stalux-sidebar-right-
|
|
362
|
-
"stalux-sidebar-right-
|
|
363
|
-
"stalux-
|
|
364
|
-
"stalux-
|
|
365
|
-
"stalux-taxonomy-enter": "kk",
|
|
366
|
-
"stalux-
|
|
367
|
-
"stalux-word-word-glow": "km"
|
|
340
|
+
"stalux-cursor-blink": "k4",
|
|
341
|
+
"stalux-link-avatar-spin": "k5",
|
|
342
|
+
"stalux-page-enter": "k6",
|
|
343
|
+
"stalux-page-enter-back": "k7",
|
|
344
|
+
"stalux-page-exit": "k8",
|
|
345
|
+
"stalux-random-spin": "k9",
|
|
346
|
+
"stalux-sidebar-left-back-in": "ka",
|
|
347
|
+
"stalux-sidebar-left-back-out": "kb",
|
|
348
|
+
"stalux-sidebar-left-in": "kc",
|
|
349
|
+
"stalux-sidebar-left-out": "kd",
|
|
350
|
+
"stalux-sidebar-right-back-in": "ke",
|
|
351
|
+
"stalux-sidebar-right-back-out": "kf",
|
|
352
|
+
"stalux-sidebar-right-in": "kg",
|
|
353
|
+
"stalux-sidebar-right-out": "kh",
|
|
354
|
+
"stalux-social-stalux-social-arrive": "ki",
|
|
355
|
+
"stalux-taxonomy-enter": "kj",
|
|
356
|
+
"stalux-taxonomy-enter-back": "kk",
|
|
357
|
+
"stalux-word-word-glow": "kl"
|
|
368
358
|
}
|
|
369
359
|
};
|
|
@@ -52,11 +52,7 @@ const walineConfig = walineSection?.serverURL
|
|
|
52
52
|
---
|
|
53
53
|
|
|
54
54
|
<div class="stalux-post-page">
|
|
55
|
-
<div
|
|
56
|
-
class="stalux-post-layout"
|
|
57
|
-
transition:name="stalux-article-layout"
|
|
58
|
-
transition:animate="none"
|
|
59
|
-
>
|
|
55
|
+
<div class="stalux-post-layout">
|
|
60
56
|
<!-- Left Sidebar -->
|
|
61
57
|
<PostLeft author={author} authorAvatar={props.authorAvatar} />
|
|
62
58
|
|
package/src/layouts/Stalux.astro
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
getPromoteData,
|
|
12
12
|
getSiteData,
|
|
13
13
|
} from "@utils/config-utils";
|
|
14
|
+
import type { SocialImage } from "@utils/social-image";
|
|
14
15
|
import { getRouteKind } from "@utils/view-transitions";
|
|
15
16
|
|
|
16
17
|
const props = Astro.props as Props;
|
|
@@ -19,6 +20,7 @@ interface Props {
|
|
|
19
20
|
title: string;
|
|
20
21
|
description: string;
|
|
21
22
|
cover?: string;
|
|
23
|
+
socialImage?: SocialImage;
|
|
22
24
|
isArticle?: boolean;
|
|
23
25
|
date?: string;
|
|
24
26
|
updated?: string;
|
|
@@ -60,6 +62,7 @@ const routeKind = getRouteKind(Astro.url.pathname);
|
|
|
60
62
|
title={props.title}
|
|
61
63
|
description={props.description}
|
|
62
64
|
cover={props.cover}
|
|
65
|
+
socialImage={props.socialImage}
|
|
63
66
|
isArticle={props.isArticle}
|
|
64
67
|
date={props.date}
|
|
65
68
|
updated={props.updated}
|
|
@@ -88,20 +91,10 @@ const routeKind = getRouteKind(Astro.url.pathname);
|
|
|
88
91
|
data-stalux-umami-id={headConfig?.umami?.id}
|
|
89
92
|
data-stalux-umami-url={headConfig?.umami?.url}
|
|
90
93
|
>
|
|
91
|
-
<header
|
|
92
|
-
class="stalux-site-header"
|
|
93
|
-
data-stalux-font-group="header"
|
|
94
|
-
transition:name="stalux-site-header"
|
|
95
|
-
transition:animate="none"
|
|
96
|
-
transition:persist="stalux-site-header"
|
|
97
|
-
>
|
|
94
|
+
<header class="stalux-site-header" data-stalux-font-group="header">
|
|
98
95
|
<Navs />
|
|
99
96
|
</header>
|
|
100
|
-
<div
|
|
101
|
-
data-stalux-scroll-sentinel
|
|
102
|
-
transition:persist="stalux-scroll-sentinel"
|
|
103
|
-
aria-hidden="true"
|
|
104
|
-
></div>
|
|
97
|
+
<div data-stalux-scroll-sentinel aria-hidden="true"></div>
|
|
105
98
|
{llmPromote && (
|
|
106
99
|
<section class="stalux-ai-prompt" data-stalux-ai-prompt aria-label="AI site context">
|
|
107
100
|
<p>{llmPromote}</p>
|
|
@@ -111,15 +104,9 @@ const routeKind = getRouteKind(Astro.url.pathname);
|
|
|
111
104
|
class="bg-layer bg-a"
|
|
112
105
|
data-stalux-background="a"
|
|
113
106
|
style="opacity: 1;"
|
|
114
|
-
transition:persist="stalux-bg-a"
|
|
115
|
-
aria-hidden="true"
|
|
116
|
-
></div>
|
|
117
|
-
<div
|
|
118
|
-
class="bg-layer bg-b"
|
|
119
|
-
data-stalux-background="b"
|
|
120
|
-
transition:persist="stalux-bg-b"
|
|
121
107
|
aria-hidden="true"
|
|
122
108
|
></div>
|
|
109
|
+
<div class="bg-layer bg-b" data-stalux-background="b" aria-hidden="true"></div>
|
|
123
110
|
<main class="stalux-main" data-stalux-route={routeKind}>
|
|
124
111
|
<slot />
|
|
125
112
|
</main>
|
package/src/pages/index.astro
CHANGED
|
@@ -20,11 +20,7 @@ import { getSiteData } from "@utils/config-utils";
|
|
|
20
20
|
|
|
21
21
|
<Stalux title={siteTitle} description={siteDescription}>
|
|
22
22
|
<div class="stalux-home-body stalux-u-flex stalux-u-items-center stalux-u-flex-col">
|
|
23
|
-
<h1
|
|
24
|
-
class="stalux-home-title stalux-u-text-center"
|
|
25
|
-
data-stalux-motion-focus
|
|
26
|
-
transition:animate="none"
|
|
27
|
-
>
|
|
23
|
+
<h1 class="stalux-home-title stalux-u-text-center" data-stalux-motion-focus>
|
|
28
24
|
{siteTitle}
|
|
29
25
|
</h1>
|
|
30
26
|
<h2
|