@xingwangzhe/stalux 1.28.5 → 1.29.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/.release/cli.mjs +1 -0
- package/README.md +6 -0
- package/package.json +1 -1
- package/src/components/stalux/archives/archivesList.astro +3 -3
- package/src/components/stalux/categories/categoriesList.astro +1 -1
- package/src/components/stalux/posts/PostTaxonomy.astro +1 -6
- package/src/components/stalux/posts/postCard.astro +5 -1
- package/src/components/stalux/posts/postContent.astro +3 -4
- package/src/components/stalux/posts/postToc.astro +1 -6
- package/src/index.ts +6 -1
- package/src/internal/image-dimensions.ts +122 -0
- package/src/layouts/PostLayout.astro +3 -0
- package/src/layouts/Stalux.astro +2 -1
- package/src/pages/posts/[post].astro +1 -0
- package/src/schemas/config.ts +5 -0
- package/src/styles/base/init.css +30 -17
- package/src/styles/components/archives/archivesList.module.css +11 -112
- package/src/styles/components/categories/categoriesCard.module.css +13 -7
- package/src/styles/components/categories/categoriesList.module.css +2 -5
- package/src/styles/components/layout/navs.module.css +4 -3
- package/src/styles/components/links/linkCard.module.css +13 -12
- package/src/styles/components/links/linkList.module.css +0 -13
- package/src/styles/components/posts/postCard.module.css +19 -21
- package/src/styles/components/posts/postContent.module.css +19 -11
- package/src/styles/components/posts/postLayout.module.css +5 -5
- package/src/styles/components/posts/postList.module.css +2 -1
- package/src/styles/components/posts/postNavigation.module.css +2 -3
- package/src/styles/components/posts/postSidebar.module.css +6 -4
- package/src/styles/components/posts/waline.css +3 -4
- package/src/styles/components/words/wordCard.module.css +9 -3
- package/src/styles/components/words/wordList.module.css +0 -13
- package/src/styles/shared/view-transitions.css +54 -234
- package/src/utils/accent-color.ts +24 -0
- package/src/utils/view-transitions.ts +30 -20
package/.release/cli.mjs
CHANGED
package/README.md
CHANGED
|
@@ -40,6 +40,12 @@ export default defineConfig({
|
|
|
40
40
|
});
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
+
Set the optional `accentColor` in `stalux/config/site.yml` to customize the shared theme and Waline accent. It accepts a six-digit HEX color and defaults to `#EAB308`; choose a color with sufficient contrast against your background.
|
|
44
|
+
|
|
45
|
+
```yaml title="stalux/config/site.yml"
|
|
46
|
+
accentColor: "#EAB308"
|
|
47
|
+
```
|
|
48
|
+
|
|
43
49
|
To customize the bundled integrations, pass options (or disable them with `false`):
|
|
44
50
|
|
|
45
51
|
```ts
|
package/package.json
CHANGED
|
@@ -58,18 +58,18 @@ const archiveYears = [...archivesByYearMonth.entries()]
|
|
|
58
58
|
{archiveYears.length > 0 ? (
|
|
59
59
|
archiveYears.map((archiveYear) => (
|
|
60
60
|
<div class={styles.yearSection} data-year={String(archiveYear.year)}>
|
|
61
|
-
<div class={styles.yearHeader}
|
|
61
|
+
<div class={styles.yearHeader}>
|
|
62
62
|
<h2 class={styles.yearTitle}>{archiveYear.year}</h2>
|
|
63
63
|
<div class={styles.yearCount}>
|
|
64
64
|
{t("archives.postCount", { n: archiveYear.count })}
|
|
65
65
|
</div>
|
|
66
66
|
</div>
|
|
67
67
|
{archiveYear.months.map(({ month, posts: monthPosts }) => (
|
|
68
|
-
<div class={styles.monthSection}
|
|
68
|
+
<div class={styles.monthSection}>
|
|
69
69
|
<h3 class={styles.monthTitle}>{t("archives.month", { n: month })}</h3>
|
|
70
70
|
<ul class={styles.postList}>
|
|
71
71
|
{monthPosts.map((post) => (
|
|
72
|
-
<li class={styles.postItem}
|
|
72
|
+
<li class={styles.postItem}>
|
|
73
73
|
<time class={styles.postDate} datetime={post.data.date}>
|
|
74
74
|
{format(parseDate(post.data.date), "yyyy-MM-dd")}
|
|
75
75
|
</time>
|
|
@@ -8,7 +8,7 @@ const categoryList = await getCategoryCountList(Astro.logger);
|
|
|
8
8
|
|
|
9
9
|
<ul class={`${styles.categoriesList} stagger-children`}>
|
|
10
10
|
{categoryList.map((category) => (
|
|
11
|
-
<li class={styles.item}
|
|
11
|
+
<li class={styles.item}>
|
|
12
12
|
<CategoriesCard category={category} />
|
|
13
13
|
</li>
|
|
14
14
|
))}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { getCollection } from "astro:content";
|
|
3
|
-
import { fade } from "astro:transitions";
|
|
4
3
|
import style from "@styles/components/posts/postTaxonomy.module.css";
|
|
5
4
|
import { getSiteData } from "@utils/config-utils";
|
|
6
5
|
import { createTranslator } from "@utils/i18n";
|
|
@@ -46,11 +45,7 @@ const cfg = {
|
|
|
46
45
|
<nav class={style.container} transition:name={cfg.containerName} aria-label={title ?? cfg.heading}>
|
|
47
46
|
{items.length > 0 && <h3 class={style.title}>{title ?? cfg.heading}</h3>}
|
|
48
47
|
{items.length > 0 ? (
|
|
49
|
-
<div
|
|
50
|
-
class={cfg.listClass}
|
|
51
|
-
transition:name={cfg.contentName}
|
|
52
|
-
transition:animate={fade({ duration: "0.3s" })}
|
|
53
|
-
>
|
|
48
|
+
<div class={cfg.listClass} transition:name={cfg.contentName}>
|
|
54
49
|
{items.map((item) => (
|
|
55
50
|
<a href={`${cfg.prefix}${item}/`} class={`${cfg.itemClass} a-none`} data-astro-prefetch>
|
|
56
51
|
{item}
|
|
@@ -4,6 +4,7 @@ import { getCollection } from "astro:content";
|
|
|
4
4
|
import style from "@styles/components/posts/postCard.module.css";
|
|
5
5
|
import { getSiteData } from "@utils/config-utils";
|
|
6
6
|
import { toMachineDateTime } from "@utils/semantic-time";
|
|
7
|
+
import { getPostTitleTransitionName } from "@utils/view-transitions";
|
|
7
8
|
|
|
8
9
|
interface Props {
|
|
9
10
|
post: CollectionEntry<"posts">;
|
|
@@ -41,7 +42,10 @@ const description = post.data.desc || "";
|
|
|
41
42
|
</div>
|
|
42
43
|
</a>
|
|
43
44
|
)}
|
|
44
|
-
<h2
|
|
45
|
+
<h2
|
|
46
|
+
class={style.postTitle}
|
|
47
|
+
transition:name={priority ? getPostTitleTransitionName(post.data.abbrlink) : undefined}
|
|
48
|
+
>
|
|
45
49
|
<a href={`/posts/${post.data.abbrlink}/`} class="a-none" data-astro-prefetch="viewport">
|
|
46
50
|
{post.data.title}
|
|
47
51
|
</a>
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
---
|
|
2
|
-
import { fade } from "astro:transitions";
|
|
3
2
|
import "photoswipe/dist/photoswipe.css";
|
|
4
3
|
import style from "@styles/components/posts/postContent.module.css";
|
|
5
4
|
import "@styles/components/markdown.css";
|
|
@@ -11,6 +10,7 @@ import { toMachineDateTime } from "@utils/semantic-time";
|
|
|
11
10
|
interface Props {
|
|
12
11
|
title: string;
|
|
13
12
|
transitionName?: string;
|
|
13
|
+
titleTransitionName?: string;
|
|
14
14
|
date?: string;
|
|
15
15
|
updated?: string;
|
|
16
16
|
readingMinutes?: number;
|
|
@@ -35,12 +35,11 @@ const mdSourceUrl = exportMd ? `${Astro.url.pathname.replace(/\/$/, "")}.md` : "
|
|
|
35
35
|
<article
|
|
36
36
|
class={style.articleContent}
|
|
37
37
|
data-stalux-gallery={props.hasImage ? "true" : undefined}
|
|
38
|
-
transition:name={props.transitionName
|
|
39
|
-
transition:animate={fade({ duration: "0.45s" })}
|
|
38
|
+
transition:name={props.transitionName}
|
|
40
39
|
>
|
|
41
40
|
<!-- Article Header -->
|
|
42
41
|
<header class={style.articleHeader}>
|
|
43
|
-
<h1 class={style.title}>{props.title}</h1>
|
|
42
|
+
<h1 class={style.title} transition:name={props.titleTransitionName}>{props.title}</h1>
|
|
44
43
|
<div class={style.metadata}>
|
|
45
44
|
{props.date && (
|
|
46
45
|
<time
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { getCollection } from "astro:content";
|
|
3
|
-
import { fade } from "astro:transitions";
|
|
4
3
|
import style from "@styles/components/posts/postToc.module.css";
|
|
5
4
|
import { getSiteData } from "@utils/config-utils";
|
|
6
5
|
import { createTranslator } from "@utils/i18n";
|
|
@@ -36,11 +35,7 @@ const tocItems = headings
|
|
|
36
35
|
>
|
|
37
36
|
<h3 class={style.title}>{title}</h3>
|
|
38
37
|
{tocItems.length > 0 ? (
|
|
39
|
-
<ul
|
|
40
|
-
class={style.list}
|
|
41
|
-
transition:name="toc-content"
|
|
42
|
-
transition:animate={fade({ duration: "0.3s" })}
|
|
43
|
-
>
|
|
38
|
+
<ul class={style.list} transition:name="toc-content">
|
|
44
39
|
{tocItems.map((heading) => (
|
|
45
40
|
<li>
|
|
46
41
|
<a
|
package/src/index.ts
CHANGED
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
resolveFontInputs,
|
|
37
37
|
writePageFontSubset,
|
|
38
38
|
} from "./internal/font-slices";
|
|
39
|
+
import { addLocalImageDimensions } from "./internal/image-dimensions";
|
|
39
40
|
import { createInjectedRoutes } from "./internal/injected-routes";
|
|
40
41
|
import { createRuntimeCacheKey } from "./internal/runtime-cache-key";
|
|
41
42
|
import {
|
|
@@ -376,7 +377,11 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
376
377
|
/<style data-stalux-page-font>[\s\S]*?<\/style>/gi,
|
|
377
378
|
"",
|
|
378
379
|
);
|
|
379
|
-
|
|
380
|
+
const withImageDimensions = await addLocalImageDimensions(
|
|
381
|
+
withoutPageFont,
|
|
382
|
+
outDir,
|
|
383
|
+
);
|
|
384
|
+
let after = applyHtmlImageLoadingPolicy(withImageDimensions);
|
|
380
385
|
if (pageFontData) {
|
|
381
386
|
const linkedStylesheetText = readLinkedStylesheetText(
|
|
382
387
|
after,
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export type ImageDimensions = { width: number; height: number };
|
|
5
|
+
|
|
6
|
+
/** Read intrinsic dimensions for common public image formats without loading an image library. */
|
|
7
|
+
export function readImageDimensions(data: Buffer): ImageDimensions | undefined {
|
|
8
|
+
if (data.length >= 24 && data.toString("hex", 0, 8) === "89504e470d0a1a0a") {
|
|
9
|
+
return { width: data.readUInt32BE(16), height: data.readUInt32BE(20) };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (
|
|
13
|
+
data.length >= 10 &&
|
|
14
|
+
(data.toString("ascii", 0, 6) === "GIF87a" || data.toString("ascii", 0, 6) === "GIF89a")
|
|
15
|
+
) {
|
|
16
|
+
return { width: data.readUInt16LE(6), height: data.readUInt16LE(8) };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (
|
|
20
|
+
data.length >= 30 &&
|
|
21
|
+
data.toString("ascii", 0, 4) === "RIFF" &&
|
|
22
|
+
data.toString("ascii", 8, 12) === "WEBP"
|
|
23
|
+
) {
|
|
24
|
+
const chunk = data.toString("ascii", 12, 16);
|
|
25
|
+
if (chunk === "VP8X") {
|
|
26
|
+
return {
|
|
27
|
+
width: 1 + data.readUIntLE(24, 3),
|
|
28
|
+
height: 1 + data.readUIntLE(27, 3),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (chunk === "VP8 " && data[23] === 0x9d && data[24] === 0x01 && data[25] === 0x2a) {
|
|
32
|
+
return {
|
|
33
|
+
width: data.readUInt16LE(26) & 0x3fff,
|
|
34
|
+
height: data.readUInt16LE(28) & 0x3fff,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (chunk === "VP8L" && data[20] === 0x2f) {
|
|
38
|
+
const b1 = data[21] ?? 0;
|
|
39
|
+
const b2 = data[22] ?? 0;
|
|
40
|
+
const b3 = data[23] ?? 0;
|
|
41
|
+
const b4 = data[24] ?? 0;
|
|
42
|
+
return {
|
|
43
|
+
width: 1 + b1 + ((b2 & 0x3f) << 8),
|
|
44
|
+
height: 1 + (b2 >> 6) + (b3 << 2) + ((b4 & 0x0f) << 10),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (data.length >= 4 && data[0] === 0xff && data[1] === 0xd8) {
|
|
50
|
+
let offset = 2;
|
|
51
|
+
while (offset + 4 <= data.length) {
|
|
52
|
+
if (data[offset] !== 0xff) break;
|
|
53
|
+
const marker = data[offset + 1] ?? 0;
|
|
54
|
+
const size = data.readUInt16BE(offset + 2);
|
|
55
|
+
if (size < 7 || offset + 2 + size > data.length) break;
|
|
56
|
+
if (
|
|
57
|
+
[
|
|
58
|
+
0xc0, 0xc1, 0xc2, 0xc3, 0xc5, 0xc6, 0xc7, 0xc9, 0xca, 0xcb, 0xcd, 0xce, 0xcf,
|
|
59
|
+
].includes(marker)
|
|
60
|
+
) {
|
|
61
|
+
return {
|
|
62
|
+
width: data.readUInt16BE(offset + 7),
|
|
63
|
+
height: data.readUInt16BE(offset + 5),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
offset += size + 2;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Add width/height to same-origin emitted images so their space is reserved before download. */
|
|
74
|
+
export async function addLocalImageDimensions(html: string, outputDir: string): Promise<string> {
|
|
75
|
+
const dimensionsCache = new Map<string, Promise<ImageDimensions | undefined>>();
|
|
76
|
+
const root = path.resolve(outputDir);
|
|
77
|
+
const tags = [...html.matchAll(/<img\b[^>]*>/gi)];
|
|
78
|
+
|
|
79
|
+
for (const match of tags) {
|
|
80
|
+
const tag = match[0];
|
|
81
|
+
if (!tag || (/\swidth=/i.test(tag) && /\sheight=/i.test(tag))) continue;
|
|
82
|
+
const src = tag.match(/\ssrc="([^"]+)"/i)?.[1];
|
|
83
|
+
if (!src) continue;
|
|
84
|
+
|
|
85
|
+
let filePath: string;
|
|
86
|
+
try {
|
|
87
|
+
const url = new URL(src, "https://stalux.invalid");
|
|
88
|
+
if (url.origin !== "https://stalux.invalid") continue;
|
|
89
|
+
filePath = path.resolve(root, `.${decodeURIComponent(url.pathname)}`);
|
|
90
|
+
} catch {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (filePath !== root && !filePath.startsWith(`${root}${path.sep}`)) continue;
|
|
94
|
+
|
|
95
|
+
let dimensions = dimensionsCache.get(filePath);
|
|
96
|
+
if (!dimensions) {
|
|
97
|
+
dimensions = readFile(filePath)
|
|
98
|
+
.then(readImageDimensions)
|
|
99
|
+
.catch(() => undefined);
|
|
100
|
+
dimensionsCache.set(filePath, dimensions);
|
|
101
|
+
}
|
|
102
|
+
const size = await dimensions;
|
|
103
|
+
if (!size || size.width <= 0 || size.height <= 0) continue;
|
|
104
|
+
|
|
105
|
+
const hasWidth = /\swidth=/i.test(tag);
|
|
106
|
+
const hasHeight = /\sheight=/i.test(tag);
|
|
107
|
+
const updated =
|
|
108
|
+
hasWidth && !hasHeight
|
|
109
|
+
? tag.replace(/\s*\/?\s*>$/, (ending) => ` height="${size.height}"${ending}`)
|
|
110
|
+
: hasHeight && !hasWidth
|
|
111
|
+
? tag.replace(
|
|
112
|
+
/\sheight="[^"]*"/i,
|
|
113
|
+
` width="${size.width}" height="${size.height}"`,
|
|
114
|
+
)
|
|
115
|
+
: tag.replace(
|
|
116
|
+
/\s*\/?\s*>$/,
|
|
117
|
+
(ending) => ` width="${size.width}" height="${size.height}"${ending}`,
|
|
118
|
+
);
|
|
119
|
+
html = html.replace(tag, updated);
|
|
120
|
+
}
|
|
121
|
+
return html;
|
|
122
|
+
}
|
|
@@ -7,12 +7,14 @@ import PostNavigation from "@components/stalux/posts/postNavigation.astro";
|
|
|
7
7
|
import PostRight from "@components/stalux/posts/postRight.astro";
|
|
8
8
|
import WalineComment from "@components/stalux/posts/WalineComment.astro";
|
|
9
9
|
import { getCommentData } from "@utils/config-utils";
|
|
10
|
+
import { getPostTitleTransitionName } from "@utils/view-transitions";
|
|
10
11
|
import { buildWalineProps } from "@utils/waline";
|
|
11
12
|
import "@styles/base/math.css";
|
|
12
13
|
import layoutStyle from "@styles/components/posts/postLayout.module.css";
|
|
13
14
|
|
|
14
15
|
interface Props {
|
|
15
16
|
title: string;
|
|
17
|
+
postId?: string | number;
|
|
16
18
|
date?: string;
|
|
17
19
|
updated?: string;
|
|
18
20
|
readingMinutes?: number;
|
|
@@ -57,6 +59,7 @@ const walineConfig = walineSection?.serverURL
|
|
|
57
59
|
<!-- Main Content -->
|
|
58
60
|
<PostContent
|
|
59
61
|
title={props.title}
|
|
62
|
+
titleTransitionName={props.postId === undefined ? undefined : getPostTitleTransitionName(props.postId)}
|
|
60
63
|
date={props.date}
|
|
61
64
|
updated={props.updated}
|
|
62
65
|
readingMinutes={props.readingMinutes}
|
package/src/layouts/Stalux.astro
CHANGED
|
@@ -8,6 +8,7 @@ import "@pagefind/component-ui/css";
|
|
|
8
8
|
import "@styles/base/init.css";
|
|
9
9
|
import "@styles/shared/stagger.css";
|
|
10
10
|
import "@styles/shared/view-transitions.css";
|
|
11
|
+
import { getAccentColorStyle } from "@utils/accent-color";
|
|
11
12
|
import {
|
|
12
13
|
getAiDiscoveryData,
|
|
13
14
|
getAuthorData,
|
|
@@ -56,7 +57,7 @@ const routeAnimation = getRouteAnimation(routeKind);
|
|
|
56
57
|
---
|
|
57
58
|
|
|
58
59
|
<!doctype html>
|
|
59
|
-
<html lang={htmlLang}>
|
|
60
|
+
<html lang={htmlLang} style={getAccentColorStyle(siteConfig.accentColor)}>
|
|
60
61
|
<Head
|
|
61
62
|
title={props.title}
|
|
62
63
|
description={props.description}
|
|
@@ -69,6 +69,7 @@ export async function getStaticPaths() {
|
|
|
69
69
|
<div style="height:3em"></div>
|
|
70
70
|
<PostLayout
|
|
71
71
|
title={props.post.data.title}
|
|
72
|
+
postId={props.post.data.abbrlink}
|
|
72
73
|
date={props.post.data.date}
|
|
73
74
|
updated={props.post.data.updated}
|
|
74
75
|
readingMinutes={flags.readingMinutes}
|
package/src/schemas/config.ts
CHANGED
|
@@ -16,6 +16,11 @@ export const siteSchema = z.object({
|
|
|
16
16
|
title: z.string().min(1, "site.title is required"),
|
|
17
17
|
url: z.url("site.url must be a valid URL"),
|
|
18
18
|
description: z.string().min(1, "site.description is required"),
|
|
19
|
+
accentColor: z
|
|
20
|
+
.string()
|
|
21
|
+
.regex(/^#[\da-fA-F]{6}$/, "site.accentColor must be a six-digit HEX color such as #EAB308")
|
|
22
|
+
.optional()
|
|
23
|
+
.default("#EAB308"),
|
|
19
24
|
seoTitle: z.string().min(1).optional(),
|
|
20
25
|
timezone: z.string().optional().default("Asia/Shanghai"),
|
|
21
26
|
canonical: z.url("site.canonical must be a valid URL").optional(),
|
package/src/styles/base/init.css
CHANGED
|
@@ -56,7 +56,7 @@ a::after {
|
|
|
56
56
|
bottom: -2px;
|
|
57
57
|
height: 1px;
|
|
58
58
|
width: 100%;
|
|
59
|
-
background: linear-gradient(90deg, var(--stalux-accent-color),
|
|
59
|
+
background: linear-gradient(90deg, var(--stalux-accent-color), var(--accent-50p));
|
|
60
60
|
transform: scaleX(0);
|
|
61
61
|
transform-origin: left;
|
|
62
62
|
transition: transform 0.25s cubic-bezier(0.25, 0.9, 0.2, 1);
|
|
@@ -110,14 +110,17 @@ ul {
|
|
|
110
110
|
:root {
|
|
111
111
|
--stalux-bg-color: #202020;
|
|
112
112
|
--stalux-bg-filter: none;
|
|
113
|
-
--stalux-text-shadow:
|
|
114
|
-
--stalux-accent-color: #
|
|
113
|
+
--stalux-text-shadow: var(--stalux-accent-color) 0.08rem 0.08rem 0.18rem;
|
|
114
|
+
--stalux-accent-color: #eab308;
|
|
115
|
+
--stalux-accent-rgb: 234, 179, 8;
|
|
116
|
+
--surface-glass: var(--black-10p);
|
|
117
|
+
--surface-glass-shadow: var(--shadow-sidebar);
|
|
115
118
|
|
|
116
119
|
/* 卡片和标签颜色 Token(可在这里统一调整主题色/透明度) */
|
|
117
|
-
--card-bg:
|
|
120
|
+
--card-bg: var(--surface-glass);
|
|
118
121
|
--card-border: rgba(255, 255, 255, 0.1);
|
|
119
|
-
--card-box-shadow:
|
|
120
|
-
--card-box-shadow-hover:
|
|
122
|
+
--card-box-shadow: var(--shadow-sidebar);
|
|
123
|
+
--card-box-shadow-hover: var(--shadow-raised-hover);
|
|
121
124
|
|
|
122
125
|
--tag-bg: rgba(255, 255, 255, 0.12);
|
|
123
126
|
--tag-bg-hover: rgba(255, 255, 255, 0.18);
|
|
@@ -152,15 +155,15 @@ ul {
|
|
|
152
155
|
--black-10p: rgba(0, 0, 0, 0.1);
|
|
153
156
|
|
|
154
157
|
/* 强调色变体 */
|
|
155
|
-
--accent-90p: rgba(
|
|
156
|
-
--accent-85p: rgba(
|
|
157
|
-
--accent-80p: rgba(
|
|
158
|
-
--accent-70p: rgba(
|
|
159
|
-
--accent-60p: rgba(
|
|
160
|
-
--accent-50p: rgba(
|
|
161
|
-
--accent-40p: rgba(
|
|
162
|
-
--accent-30p: rgba(
|
|
163
|
-
--accent-20p: rgba(
|
|
158
|
+
--accent-90p: rgba(var(--stalux-accent-rgb), 0.9);
|
|
159
|
+
--accent-85p: rgba(var(--stalux-accent-rgb), 0.85);
|
|
160
|
+
--accent-80p: rgba(var(--stalux-accent-rgb), 0.8);
|
|
161
|
+
--accent-70p: rgba(var(--stalux-accent-rgb), 0.7);
|
|
162
|
+
--accent-60p: rgba(var(--stalux-accent-rgb), 0.6);
|
|
163
|
+
--accent-50p: rgba(var(--stalux-accent-rgb), 0.5);
|
|
164
|
+
--accent-40p: rgba(var(--stalux-accent-rgb), 0.4);
|
|
165
|
+
--accent-30p: rgba(var(--stalux-accent-rgb), 0.3);
|
|
166
|
+
--accent-20p: rgba(var(--stalux-accent-rgb), 0.2);
|
|
164
167
|
|
|
165
168
|
/* 常用过渡和动画 */
|
|
166
169
|
--transition-fast: 0.18s ease;
|
|
@@ -193,7 +196,14 @@ ul {
|
|
|
193
196
|
--shadow-md: 0 6px 16px rgba(0, 0, 0, 0.2);
|
|
194
197
|
--shadow-lg: 0 8px 20px rgba(0, 0, 0, 0.3);
|
|
195
198
|
--shadow-xl: 0 12px 26px rgba(0, 0, 0, 0.28);
|
|
196
|
-
--shadow-sidebar:
|
|
199
|
+
--shadow-sidebar:
|
|
200
|
+
0 16px 20px -12px rgba(0, 0, 0, 0.84),
|
|
201
|
+
10px 4px 16px -12px rgba(0, 0, 0, 0.58),
|
|
202
|
+
-10px 4px 16px -12px rgba(0, 0, 0, 0.58);
|
|
203
|
+
--shadow-raised-hover:
|
|
204
|
+
0 20px 24px -13px rgba(0, 0, 0, 0.9),
|
|
205
|
+
12px 5px 18px -13px rgba(0, 0, 0, 0.68),
|
|
206
|
+
-12px 5px 18px -13px rgba(0, 0, 0, 0.68);
|
|
197
207
|
--shadow-dark: rgba(0, 0, 0, 0.753);
|
|
198
208
|
|
|
199
209
|
/* 语义化颜色 */
|
|
@@ -269,5 +279,8 @@ footer {
|
|
|
269
279
|
padding: var(--space-lg) var(--space-md);
|
|
270
280
|
text-align: center;
|
|
271
281
|
color: var(--muted-text);
|
|
272
|
-
background:
|
|
282
|
+
background: linear-gradient(180deg, rgba(12, 14, 18, 0.18), rgba(12, 14, 18, 0.72));
|
|
283
|
+
border-top: 1px solid var(--white-10p);
|
|
284
|
+
backdrop-filter: blur(16px);
|
|
285
|
+
-webkit-backdrop-filter: blur(16px);
|
|
273
286
|
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
--tl-line: var(--radius-sm);
|
|
10
10
|
--tl-pad: var(--space-xl);
|
|
11
11
|
padding-left: var(--tl-pad);
|
|
12
|
+
animation: contentReveal 220ms ease 360ms both;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
.timelineContainer::before {
|
|
@@ -78,118 +79,7 @@
|
|
|
78
79
|
background-color: var(--black-10p);
|
|
79
80
|
border-radius: 0.5rem;
|
|
80
81
|
width: 100%;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
@keyframes fadeInUp {
|
|
84
|
-
from {
|
|
85
|
-
opacity: 0;
|
|
86
|
-
transform: translateY(20px);
|
|
87
|
-
}
|
|
88
|
-
to {
|
|
89
|
-
opacity: 1;
|
|
90
|
-
transform: translateY(0);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
@keyframes fadeInLeft {
|
|
95
|
-
from {
|
|
96
|
-
opacity: 0;
|
|
97
|
-
transform: translateX(-15px);
|
|
98
|
-
}
|
|
99
|
-
to {
|
|
100
|
-
opacity: 1;
|
|
101
|
-
transform: translateX(0);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/* yearSection 子元素通用入场动画 */
|
|
106
|
-
[data-year-header],
|
|
107
|
-
[data-month-section],
|
|
108
|
-
[data-post-item] {
|
|
109
|
-
animation: fadeInUp 0.53s ease both;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
[data-year-header] {
|
|
113
|
-
animation-duration: 0.38s;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
[data-post-item] {
|
|
117
|
-
animation-name: fadeInLeft;
|
|
118
|
-
animation-duration: 0.38s;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/* yearSection 内部阶梯延迟(yearHeader + monthSections 同为子元素) */
|
|
122
|
-
[data-year-header]:nth-child(1) {
|
|
123
|
-
animation-delay: 0s;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
[data-month-section]:nth-child(2) {
|
|
127
|
-
animation-delay: 0.18s;
|
|
128
|
-
}
|
|
129
|
-
[data-month-section]:nth-child(3) {
|
|
130
|
-
animation-delay: 0.24s;
|
|
131
|
-
}
|
|
132
|
-
[data-month-section]:nth-child(4) {
|
|
133
|
-
animation-delay: 0.3s;
|
|
134
|
-
}
|
|
135
|
-
[data-month-section]:nth-child(5) {
|
|
136
|
-
animation-delay: 0.36s;
|
|
137
|
-
}
|
|
138
|
-
[data-month-section]:nth-child(6) {
|
|
139
|
-
animation-delay: 0.42s;
|
|
140
|
-
}
|
|
141
|
-
[data-month-section]:nth-child(7) {
|
|
142
|
-
animation-delay: 0.48s;
|
|
143
|
-
}
|
|
144
|
-
[data-month-section]:nth-child(8) {
|
|
145
|
-
animation-delay: 0.54s;
|
|
146
|
-
}
|
|
147
|
-
[data-month-section]:nth-child(9) {
|
|
148
|
-
animation-delay: 0.6s;
|
|
149
|
-
}
|
|
150
|
-
[data-month-section]:nth-child(10) {
|
|
151
|
-
animation-delay: 0.66s;
|
|
152
|
-
}
|
|
153
|
-
[data-month-section]:nth-child(11) {
|
|
154
|
-
animation-delay: 0.72s;
|
|
155
|
-
}
|
|
156
|
-
[data-month-section]:nth-child(12) {
|
|
157
|
-
animation-delay: 0.78s;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/* postItem 阶梯延迟 */
|
|
161
|
-
[data-post-item]:nth-child(1) {
|
|
162
|
-
animation-delay: 0s;
|
|
163
|
-
}
|
|
164
|
-
[data-post-item]:nth-child(2) {
|
|
165
|
-
animation-delay: 0.045s;
|
|
166
|
-
}
|
|
167
|
-
[data-post-item]:nth-child(3) {
|
|
168
|
-
animation-delay: 0.09s;
|
|
169
|
-
}
|
|
170
|
-
[data-post-item]:nth-child(4) {
|
|
171
|
-
animation-delay: 0.135s;
|
|
172
|
-
}
|
|
173
|
-
[data-post-item]:nth-child(5) {
|
|
174
|
-
animation-delay: 0.18s;
|
|
175
|
-
}
|
|
176
|
-
[data-post-item]:nth-child(6) {
|
|
177
|
-
animation-delay: 0.225s;
|
|
178
|
-
}
|
|
179
|
-
[data-post-item]:nth-child(7) {
|
|
180
|
-
animation-delay: 0.27s;
|
|
181
|
-
}
|
|
182
|
-
[data-post-item]:nth-child(8) {
|
|
183
|
-
animation-delay: 0.315s;
|
|
184
|
-
}
|
|
185
|
-
[data-post-item]:nth-child(9) {
|
|
186
|
-
animation-delay: 0.36s;
|
|
187
|
-
}
|
|
188
|
-
[data-post-item]:nth-child(10) {
|
|
189
|
-
animation-delay: 0.405s;
|
|
190
|
-
}
|
|
191
|
-
[data-post-item]:nth-child(n + 11) {
|
|
192
|
-
animation-delay: 0.45s;
|
|
82
|
+
animation: stalux-content-reveal 220ms ease 360ms both;
|
|
193
83
|
}
|
|
194
84
|
|
|
195
85
|
/* 懒加载指示器样式 */
|
|
@@ -374,3 +264,12 @@
|
|
|
374
264
|
font-size: 1.1rem;
|
|
375
265
|
}
|
|
376
266
|
}
|
|
267
|
+
|
|
268
|
+
@keyframes contentReveal {
|
|
269
|
+
from {
|
|
270
|
+
opacity: 0;
|
|
271
|
+
}
|
|
272
|
+
to {
|
|
273
|
+
opacity: 1;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
@@ -4,11 +4,14 @@
|
|
|
4
4
|
display: flex;
|
|
5
5
|
flex-direction: column;
|
|
6
6
|
padding: var(--space-lg);
|
|
7
|
-
background-color: var(--
|
|
7
|
+
background-color: var(--surface-glass);
|
|
8
8
|
border-radius: 0.8rem;
|
|
9
|
-
border: var(--border-thin) solid var(--white-10p);
|
|
10
9
|
color: var(--white-full);
|
|
11
|
-
|
|
10
|
+
box-shadow: var(--shadow-sidebar);
|
|
11
|
+
transition:
|
|
12
|
+
transform 220ms ease,
|
|
13
|
+
background-color 220ms ease,
|
|
14
|
+
box-shadow 220ms ease;
|
|
12
15
|
overflow: hidden;
|
|
13
16
|
height: fit-content;
|
|
14
17
|
}
|
|
@@ -27,15 +30,14 @@
|
|
|
27
30
|
rgba(255, 255, 255, 0)
|
|
28
31
|
);
|
|
29
32
|
transform: translateX(-100%);
|
|
30
|
-
transition: transform
|
|
33
|
+
transition: transform 360ms ease;
|
|
31
34
|
z-index: 1;
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
.categoryCard:hover {
|
|
35
38
|
transform: translateY(-3px);
|
|
36
39
|
background-color: rgba(255, 255, 255, 0.08);
|
|
37
|
-
|
|
38
|
-
box-shadow: var(--shadow-lg);
|
|
40
|
+
box-shadow: var(--shadow-raised-hover);
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
.categoryCard:hover::before {
|
|
@@ -71,7 +73,11 @@
|
|
|
71
73
|
width: 28px;
|
|
72
74
|
height: 28px;
|
|
73
75
|
color: var(--white-50p);
|
|
74
|
-
transition:
|
|
76
|
+
transition:
|
|
77
|
+
transform 240ms ease,
|
|
78
|
+
background-color 240ms ease,
|
|
79
|
+
color 240ms ease,
|
|
80
|
+
box-shadow 240ms ease;
|
|
75
81
|
z-index: 2;
|
|
76
82
|
transform-origin: center center;
|
|
77
83
|
background: var(--black-15p);
|
|
@@ -11,17 +11,14 @@
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
.item {
|
|
14
|
-
animation:
|
|
15
|
-
opacity: 0;
|
|
14
|
+
animation: contentReveal 220ms ease 360ms both;
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
@keyframes
|
|
17
|
+
@keyframes contentReveal {
|
|
19
18
|
from {
|
|
20
19
|
opacity: 0;
|
|
21
|
-
transform: translateY(20px);
|
|
22
20
|
}
|
|
23
21
|
to {
|
|
24
22
|
opacity: 1;
|
|
25
|
-
transform: translateY(0);
|
|
26
23
|
}
|
|
27
24
|
}
|