@xingwangzhe/stalux 1.20.2 → 1.20.3
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 +1 -1
- package/package.json +3 -4
- package/src/components/stalux/layout/head.astro +2 -2
- package/src/components/stalux/posts/WalineComment.astro +6 -4
- package/src/components/stalux/posts/postContent.astro +3 -2
- package/src/components/stalux/posts/postLeft.astro +2 -2
- package/src/components/stalux/posts/postRight.astro +2 -2
- package/src/components/stalux/posts/postToc.astro +0 -13
- package/src/layouts/Stalux.astro +22 -3
- package/src/pages/404.astro +30 -12
- package/src/pages/about.astro +1 -0
- package/src/pages/posts/[post].md.ts +4 -11
- package/src/scripts/view-transitions.ts +49 -0
- package/src/scripts/webmcp.ts +2 -2
- package/src/styles/shared/view-transitions.css +356 -0
- package/src/utils/view-transitions.ts +134 -0
package/README.md
CHANGED
|
@@ -193,7 +193,7 @@ When a WebMCP-aware browser opens your site, these read-only tools are registere
|
|
|
193
193
|
| `stalux_current_post` | Metadata of the post currently being viewed | `/api/posts.json` |
|
|
194
194
|
| `stalux_random_post` | Pick a random post's metadata | `/api/posts.json` |
|
|
195
195
|
| `stalux_search_posts` | Full-text search across posts | Pagefind `/pagefind/` |
|
|
196
|
-
| `stalux_read_post` | Fetch a post's
|
|
196
|
+
| `stalux_read_post` | Fetch a post's normalized Markdown | `/posts/{abbrlink}.md` |
|
|
197
197
|
| `stalux_site_info` | Site title, URL, description + pointers to `llms.txt` / `llms-full.txt` | `site.yml` (build-time) |
|
|
198
198
|
|
|
199
199
|
All tools are `readOnlyHint: true` — they never modify any state.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xingwangzhe/stalux",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.3",
|
|
4
4
|
"description": "A powerful, modern Astro blog theme — use as template or install as plugin",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astro",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"@astrojs/rss": "^4.0.19",
|
|
79
79
|
"@astrojs/sitemap": "^3.7.3",
|
|
80
80
|
"@expressive-code/plugin-line-numbers": "^0.44.1",
|
|
81
|
-
"@lucide/astro": "^1.
|
|
81
|
+
"@lucide/astro": "^1.31.0",
|
|
82
82
|
"@mcp-b/webmcp-polyfill": "^4.0.0",
|
|
83
83
|
"@pagefind/component-ui": "^1.5.2",
|
|
84
84
|
"@waline/client": "^3.15.2",
|
|
@@ -99,9 +99,8 @@
|
|
|
99
99
|
"temml": "^0.13.4"
|
|
100
100
|
},
|
|
101
101
|
"devDependencies": {
|
|
102
|
-
"@types/node": "^26.1.2",
|
|
103
102
|
"astro": "^7.2.0",
|
|
104
|
-
"knip": "^6.
|
|
103
|
+
"knip": "^6.32.0",
|
|
105
104
|
"license-checker-rseidelsohn": "5.0.1",
|
|
106
105
|
"oxfmt": "^0.62.0",
|
|
107
106
|
"oxlint": "^1.77.0",
|
|
@@ -157,9 +157,9 @@ if (isArticle) {
|
|
|
157
157
|
const ldJson = JSON.stringify(schema, null, 2).replace(/<\//g, "<\\/");
|
|
158
158
|
---
|
|
159
159
|
|
|
160
|
-
<head
|
|
160
|
+
<head>
|
|
161
161
|
<meta charset="utf-8" />
|
|
162
|
-
<ClientRouter />
|
|
162
|
+
<ClientRouter fallback="animate" />
|
|
163
163
|
<SEO
|
|
164
164
|
title={effectiveTitle}
|
|
165
165
|
description={effectiveDescription}
|
|
@@ -66,10 +66,12 @@ const walineConfig = {
|
|
|
66
66
|
class="waline-comment-container"
|
|
67
67
|
aria-label={t("comment.title")}
|
|
68
68
|
data-waline-config={
|
|
69
|
-
// base64 可逆混淆(非加密,仅避免 SSR
|
|
70
|
-
|
|
71
|
-
new TextEncoder().encode(JSON.stringify(walineConfig)),
|
|
72
|
-
|
|
69
|
+
// base64 可逆混淆(非加密,仅避免 SSR 注入/转义问题)
|
|
70
|
+
btoa(
|
|
71
|
+
Array.from(new TextEncoder().encode(JSON.stringify(walineConfig)), (byte) =>
|
|
72
|
+
String.fromCharCode(byte),
|
|
73
|
+
).join(""),
|
|
74
|
+
)
|
|
73
75
|
}
|
|
74
76
|
>
|
|
75
77
|
<h2 class="waline-comment-title">{t("comment.title")}</h2>
|
|
@@ -9,6 +9,7 @@ import { getSiteData, getAuthorData, getHeadData, getNavsData, getLinksData, get
|
|
|
9
9
|
|
|
10
10
|
interface Props {
|
|
11
11
|
title: string;
|
|
12
|
+
transitionName?: string;
|
|
12
13
|
date?: string;
|
|
13
14
|
updated?: string;
|
|
14
15
|
readingTime?: string;
|
|
@@ -35,8 +36,8 @@ const mdSourceUrl = exportMd
|
|
|
35
36
|
|
|
36
37
|
<article
|
|
37
38
|
class={style.articleContent}
|
|
38
|
-
transition:name="post-content"
|
|
39
|
-
transition:animate={fade({ duration: "
|
|
39
|
+
transition:name={props.transitionName ?? "post-content"}
|
|
40
|
+
transition:animate={fade({ duration: "0.45s" })}>
|
|
40
41
|
<!-- Article Header -->
|
|
41
42
|
<header class={style.articleHeader}>
|
|
42
43
|
<h1 class={style.title}>{props.title}</h1>
|
|
@@ -3,7 +3,7 @@ import PostAuthor from "@components/stalux/posts/postAuthor.astro";
|
|
|
3
3
|
import style from "@styles/components/posts/postSidebar.module.css";
|
|
4
4
|
import SocialMedia from "@components/stalux/links/socialMedia.astro";
|
|
5
5
|
import RandomPost from "@components/stalux/posts/randomPost.astro";
|
|
6
|
-
import {
|
|
6
|
+
import { getSidebarAnimation } from "@utils/view-transitions";
|
|
7
7
|
// 判断是否为文章页面
|
|
8
8
|
const isPostPage = Astro.url.pathname.includes("/posts/");
|
|
9
9
|
---
|
|
@@ -11,7 +11,7 @@ const isPostPage = Astro.url.pathname.includes("/posts/");
|
|
|
11
11
|
<aside
|
|
12
12
|
class={style.sidebar}
|
|
13
13
|
transition:name="left-sidebar"
|
|
14
|
-
transition:animate={
|
|
14
|
+
transition:animate={getSidebarAnimation("left")}
|
|
15
15
|
>
|
|
16
16
|
<PostAuthor />
|
|
17
17
|
<div class={style.sidebarContainer}>
|
|
@@ -3,7 +3,7 @@ import style from "@styles/components/posts/postSidebar.module.css";
|
|
|
3
3
|
import Date from "@components/stalux/common/date.astro";
|
|
4
4
|
import PostToc from "@components/stalux/posts/postToc.astro";
|
|
5
5
|
import PostTaxonomy from "@components/stalux/posts/PostTaxonomy.astro";
|
|
6
|
-
import {
|
|
6
|
+
import { getSidebarAnimation } from "@utils/view-transitions";
|
|
7
7
|
interface Props {
|
|
8
8
|
headings?: Array<{ depth: number; slug: string; text: string }>;
|
|
9
9
|
tags?: string[];
|
|
@@ -16,7 +16,7 @@ const { headings = [], tags = [], categories = [] } = Astro.props;
|
|
|
16
16
|
<aside
|
|
17
17
|
class={style.sidebarRight}
|
|
18
18
|
transition:name="right-sidebar"
|
|
19
|
-
transition:animate={
|
|
19
|
+
transition:animate={getSidebarAnimation("right")}
|
|
20
20
|
>
|
|
21
21
|
<div class={style.sidebarContainer}>
|
|
22
22
|
<Date />
|
|
@@ -28,19 +28,6 @@ const tocItems = headings
|
|
|
28
28
|
depth: Math.min(Math.max(item.depth ?? 1, 1), 6),
|
|
29
29
|
}));
|
|
30
30
|
|
|
31
|
-
// 定义目录内容的过渡动画 - 平滑褶皱效果
|
|
32
|
-
const tocContentAnimation = {
|
|
33
|
-
old: {
|
|
34
|
-
name: "fadeSlideDown",
|
|
35
|
-
duration: "0.3s",
|
|
36
|
-
easing: "ease-out",
|
|
37
|
-
},
|
|
38
|
-
new: {
|
|
39
|
-
name: "fadeSlideDown",
|
|
40
|
-
duration: "0.3s",
|
|
41
|
-
easing: "ease-out",
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
31
|
---
|
|
45
32
|
|
|
46
33
|
<nav transition:name="toc-container" data-toc-active-class={style.active} aria-label={t("taxonomy.toc")}>
|
package/src/layouts/Stalux.astro
CHANGED
|
@@ -4,7 +4,9 @@ import Footer from "@components/stalux/layout/footer.astro";
|
|
|
4
4
|
import { getCollection } from "astro:content";
|
|
5
5
|
import "@styles/base/init.css";
|
|
6
6
|
import "@styles/shared/stagger.css";
|
|
7
|
+
import "@styles/shared/view-transitions.css";
|
|
7
8
|
import { getAuthorData, getAiDiscoveryData, getPromoteData, getSiteData } from "@utils/config-utils";
|
|
9
|
+
import { getRouteAnimation, getRouteKind } from "@utils/view-transitions";
|
|
8
10
|
// font-subset.ts 构建期生成的 manifest 副本(进入 Astro 依赖图,
|
|
9
11
|
// manifest 变化 → 页面重建 → 增量构建 restored 的页面不会引用旧字体资源)
|
|
10
12
|
import fontsManifest from "../internal/generated/fonts-manifest.json";
|
|
@@ -76,6 +78,8 @@ const llmPromote = llmPromoteRaw
|
|
|
76
78
|
?.replace(/\{url\}/g, siteConfig.url)
|
|
77
79
|
?.replace(/\{title\}/g, siteConfig.title)
|
|
78
80
|
?.replace(/\{cc\}/g, "CC-BY-NC-SA-4.0");
|
|
81
|
+
const routeKind = getRouteKind(Astro.url.pathname);
|
|
82
|
+
const routeAnimation = getRouteAnimation(routeKind);
|
|
79
83
|
---
|
|
80
84
|
|
|
81
85
|
<!doctype html>
|
|
@@ -97,6 +101,7 @@ const llmPromote = llmPromoteRaw
|
|
|
97
101
|
{fontSubsetUrl && <link rel="stylesheet" href={fontSubsetUrl} />}
|
|
98
102
|
<script>
|
|
99
103
|
import "../scripts/background.ts";
|
|
104
|
+
import "../scripts/view-transitions.ts";
|
|
100
105
|
</script>
|
|
101
106
|
{
|
|
102
107
|
aiConfig?.conformance !== "disabled" && (
|
|
@@ -128,9 +133,23 @@ const llmPromote = llmPromoteRaw
|
|
|
128
133
|
data-stalux-bg-index="0"
|
|
129
134
|
>
|
|
130
135
|
{llmPromote && <div style="display:none">{llmPromote}</div>}
|
|
131
|
-
<div
|
|
132
|
-
|
|
133
|
-
|
|
136
|
+
<div
|
|
137
|
+
class="bg-layer bg-a"
|
|
138
|
+
style="opacity: 1;"
|
|
139
|
+
transition:persist="stalux-bg-a"
|
|
140
|
+
aria-hidden="true"
|
|
141
|
+
></div>
|
|
142
|
+
<div
|
|
143
|
+
class="bg-layer bg-b"
|
|
144
|
+
transition:persist="stalux-bg-b"
|
|
145
|
+
aria-hidden="true"
|
|
146
|
+
></div>
|
|
147
|
+
<main
|
|
148
|
+
class="stalux-main"
|
|
149
|
+
data-stalux-route={routeKind}
|
|
150
|
+
transition:name="stalux-page"
|
|
151
|
+
transition:animate={routeAnimation}
|
|
152
|
+
>
|
|
134
153
|
<slot />
|
|
135
154
|
</main>
|
|
136
155
|
<Footer />
|
package/src/pages/404.astro
CHANGED
|
@@ -26,18 +26,36 @@ const { t } = createTranslator(siteConfig.lang || "zh-CN");
|
|
|
26
26
|
</div>
|
|
27
27
|
</div>
|
|
28
28
|
|
|
29
|
-
<script define:vars={{ countdownText: t("page404.countdown", { n: "{n}" }) }}>
|
|
30
|
-
let
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
29
|
+
<script define:vars={{ countdownText: t("page404.countdown", { n: "{n}" }) }} is:inline>
|
|
30
|
+
let timer: number | undefined;
|
|
31
|
+
|
|
32
|
+
function startCountdown() {
|
|
33
|
+
if (timer !== undefined) window.clearInterval(timer);
|
|
34
|
+
let seconds = 5;
|
|
35
|
+
const el = document.getElementById("countdown");
|
|
36
|
+
if (!el) return;
|
|
37
|
+
|
|
38
|
+
timer = window.setInterval(() => {
|
|
39
|
+
seconds--;
|
|
40
|
+
if (seconds <= 0) {
|
|
41
|
+
if (timer !== undefined) window.clearInterval(timer);
|
|
42
|
+
timer = undefined;
|
|
43
|
+
const home = document.querySelector<HTMLAnchorElement>(".home-btn");
|
|
44
|
+
home?.click();
|
|
45
|
+
} else {
|
|
46
|
+
el.textContent = countdownText.replace("{n}", String(seconds));
|
|
47
|
+
}
|
|
48
|
+
}, 1000);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function stopCountdown() {
|
|
52
|
+
if (timer !== undefined) window.clearInterval(timer);
|
|
53
|
+
timer = undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
document.addEventListener("astro:before-swap", stopCountdown);
|
|
57
|
+
document.addEventListener("astro:page-load", startCountdown);
|
|
58
|
+
startCountdown();
|
|
41
59
|
</script>
|
|
42
60
|
</Stalux>
|
|
43
61
|
|
package/src/pages/about.astro
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
1
|
import { isMarkdownExportEnabled, loadConfig, type ConfigMap } from "@utils/ai-discovery";
|
|
5
2
|
import { buildCCName, buildCCLink } from "@utils/cc";
|
|
6
3
|
import { createTranslator } from "@utils/i18n";
|
|
@@ -89,15 +86,11 @@ export const GET: APIRoute = async ({ props }) => {
|
|
|
89
86
|
|
|
90
87
|
const { post } = props as { post: CollectionEntry<"posts"> };
|
|
91
88
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
markdown = await readFile(abs, "utf-8");
|
|
96
|
-
} else {
|
|
97
|
-
markdown = reconstructMarkdown(post);
|
|
98
|
-
}
|
|
89
|
+
// 内容集合已经提供解析后的 frontmatter 和正文,导出一份内容等价的 Markdown。
|
|
90
|
+
// 不读取 post.filePath,避免把页面端点绑定到 Node 文件系统。
|
|
91
|
+
let markdown = reconstructMarkdown(post);
|
|
99
92
|
|
|
100
|
-
//
|
|
93
|
+
// 追加版权脚注(不修改内容源文件)
|
|
101
94
|
const footer = generateCCFooter(post, config);
|
|
102
95
|
markdown += "\n\n---\n\n" + footer;
|
|
103
96
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { getRouteKind } from "@utils/view-transitions";
|
|
2
|
+
|
|
3
|
+
interface TransitionWindow extends Window {
|
|
4
|
+
__STALUX_VT_LISTENER__?: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function setRouteState(pathname: string): void {
|
|
8
|
+
const html = document.documentElement;
|
|
9
|
+
const route = getRouteKind(pathname);
|
|
10
|
+
html.dataset.staluxRoute = route;
|
|
11
|
+
document.querySelector("main.stalux-main")?.setAttribute("data-stalux-route", route);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface NavigationEvent {
|
|
15
|
+
from: URL;
|
|
16
|
+
to: URL;
|
|
17
|
+
direction: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function setNavigationState(event: NavigationEvent): void {
|
|
21
|
+
const from = getRouteKind(new URL(event.from, window.location.href).pathname);
|
|
22
|
+
const to = getRouteKind(new URL(event.to, window.location.href).pathname);
|
|
23
|
+
const html = document.documentElement;
|
|
24
|
+
html.dataset.staluxVtFrom = from;
|
|
25
|
+
html.dataset.staluxVtTo = to;
|
|
26
|
+
html.dataset.staluxVtDirection = event.direction;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const transitionWindow = window as TransitionWindow;
|
|
30
|
+
if (!transitionWindow.__STALUX_VT_LISTENER__) {
|
|
31
|
+
transitionWindow.__STALUX_VT_LISTENER__ = true;
|
|
32
|
+
|
|
33
|
+
document.addEventListener("astro:before-preparation", (event) => {
|
|
34
|
+
setNavigationState(event as NavigationEvent);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
document.addEventListener("astro:after-swap", () => {
|
|
38
|
+
setRouteState(window.location.pathname);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
document.addEventListener("astro:page-load", () => {
|
|
42
|
+
setRouteState(window.location.pathname);
|
|
43
|
+
delete document.documentElement.dataset.staluxVtFrom;
|
|
44
|
+
delete document.documentElement.dataset.staluxVtTo;
|
|
45
|
+
delete document.documentElement.dataset.staluxVtDirection;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
setRouteState(window.location.pathname);
|
package/src/scripts/webmcp.ts
CHANGED
|
@@ -435,10 +435,10 @@ function readPostTool(): WebMCPTool {
|
|
|
435
435
|
name: "stalux_read_post",
|
|
436
436
|
title: pick("读取文章 Markdown", "Read post Markdown"),
|
|
437
437
|
description: pick(
|
|
438
|
-
"按 abbrlink(文章永久链接 ID
|
|
438
|
+
"按 abbrlink(文章永久链接 ID)读取一篇博客文章的 Markdown 全文,包含规范化 frontmatter、正文与版权脚注。" +
|
|
439
439
|
"需要文章完整内容、引用或摘要时使用;只要元信息用 stalux_get_post(更便宜)。" +
|
|
440
440
|
"获取 abbrlink 可先用 stalux_list_posts 或 stalux_search_posts。",
|
|
441
|
-
"Read the full
|
|
441
|
+
"Read the full Markdown of a post by abbrlink (permanent link ID), including normalized frontmatter, body and copyright footer. " +
|
|
442
442
|
"Use it for full content, quotes or summarization; for metadata only, stalux_get_post is cheaper. " +
|
|
443
443
|
"To get an abbrlink, first use stalux_list_posts or stalux_search_posts.",
|
|
444
444
|
),
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/* 页面外壳的路由专属入场动画;列表和卡片继续使用各自的 stagger。 */
|
|
2
|
+
@keyframes stalux-sidebar-left-out {
|
|
3
|
+
from {
|
|
4
|
+
opacity: 1;
|
|
5
|
+
transform: translateX(0);
|
|
6
|
+
}
|
|
7
|
+
to {
|
|
8
|
+
opacity: 0;
|
|
9
|
+
transform: translateX(-34px);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@keyframes stalux-sidebar-left-in {
|
|
14
|
+
from {
|
|
15
|
+
opacity: 0;
|
|
16
|
+
transform: translateX(-34px);
|
|
17
|
+
}
|
|
18
|
+
to {
|
|
19
|
+
opacity: 1;
|
|
20
|
+
transform: translateX(0);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@keyframes stalux-sidebar-left-back-in {
|
|
25
|
+
from {
|
|
26
|
+
opacity: 0;
|
|
27
|
+
transform: translateX(34px);
|
|
28
|
+
}
|
|
29
|
+
to {
|
|
30
|
+
opacity: 1;
|
|
31
|
+
transform: translateX(0);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@keyframes stalux-sidebar-left-back-out {
|
|
36
|
+
from {
|
|
37
|
+
opacity: 1;
|
|
38
|
+
transform: translateX(0);
|
|
39
|
+
}
|
|
40
|
+
to {
|
|
41
|
+
opacity: 0;
|
|
42
|
+
transform: translateX(34px);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@keyframes stalux-sidebar-right-in {
|
|
47
|
+
from {
|
|
48
|
+
opacity: 0;
|
|
49
|
+
transform: translateX(34px);
|
|
50
|
+
}
|
|
51
|
+
to {
|
|
52
|
+
opacity: 1;
|
|
53
|
+
transform: translateX(0);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@keyframes stalux-sidebar-right-back-in {
|
|
58
|
+
from {
|
|
59
|
+
opacity: 0;
|
|
60
|
+
transform: translateX(-34px);
|
|
61
|
+
}
|
|
62
|
+
to {
|
|
63
|
+
opacity: 1;
|
|
64
|
+
transform: translateX(0);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@keyframes stalux-sidebar-right-out {
|
|
69
|
+
from {
|
|
70
|
+
opacity: 1;
|
|
71
|
+
transform: translateX(0);
|
|
72
|
+
}
|
|
73
|
+
to {
|
|
74
|
+
opacity: 0;
|
|
75
|
+
transform: translateX(34px);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@keyframes stalux-sidebar-right-back-out {
|
|
80
|
+
from {
|
|
81
|
+
opacity: 1;
|
|
82
|
+
transform: translateX(0);
|
|
83
|
+
}
|
|
84
|
+
to {
|
|
85
|
+
opacity: 0;
|
|
86
|
+
transform: translateX(-34px);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@keyframes stalux-page-fade-out {
|
|
91
|
+
from {
|
|
92
|
+
opacity: 1;
|
|
93
|
+
}
|
|
94
|
+
to {
|
|
95
|
+
opacity: 0;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@keyframes stalux-page-home-in {
|
|
100
|
+
from {
|
|
101
|
+
opacity: 0;
|
|
102
|
+
transform: scale(0.98);
|
|
103
|
+
}
|
|
104
|
+
to {
|
|
105
|
+
opacity: 1;
|
|
106
|
+
transform: scale(1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@keyframes stalux-page-home-back-in {
|
|
111
|
+
from {
|
|
112
|
+
opacity: 0;
|
|
113
|
+
transform: scale(1.02);
|
|
114
|
+
}
|
|
115
|
+
to {
|
|
116
|
+
opacity: 1;
|
|
117
|
+
transform: scale(1);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@keyframes stalux-page-vertical-in {
|
|
122
|
+
from {
|
|
123
|
+
opacity: 0;
|
|
124
|
+
transform: translateY(14px);
|
|
125
|
+
}
|
|
126
|
+
to {
|
|
127
|
+
opacity: 1;
|
|
128
|
+
transform: translateY(0);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@keyframes stalux-page-vertical-back-in {
|
|
133
|
+
from {
|
|
134
|
+
opacity: 0;
|
|
135
|
+
transform: translateY(-14px);
|
|
136
|
+
}
|
|
137
|
+
to {
|
|
138
|
+
opacity: 1;
|
|
139
|
+
transform: translateY(0);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@keyframes stalux-page-grid-in {
|
|
144
|
+
from {
|
|
145
|
+
opacity: 0;
|
|
146
|
+
transform: translateY(8px) scale(0.975);
|
|
147
|
+
}
|
|
148
|
+
to {
|
|
149
|
+
opacity: 1;
|
|
150
|
+
transform: translateY(0) scale(1);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
@keyframes stalux-page-category-grid-in {
|
|
155
|
+
from {
|
|
156
|
+
opacity: 0;
|
|
157
|
+
transform: translateY(8px) scale(0.975);
|
|
158
|
+
}
|
|
159
|
+
to {
|
|
160
|
+
opacity: 1;
|
|
161
|
+
transform: translateY(0) scale(1);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
@keyframes stalux-page-grid-back-in {
|
|
166
|
+
from {
|
|
167
|
+
opacity: 0;
|
|
168
|
+
transform: translateY(-8px) scale(0.985);
|
|
169
|
+
}
|
|
170
|
+
to {
|
|
171
|
+
opacity: 1;
|
|
172
|
+
transform: translateY(0) scale(1);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@keyframes stalux-page-category-grid-back-in {
|
|
177
|
+
from {
|
|
178
|
+
opacity: 0;
|
|
179
|
+
transform: translateY(-8px) scale(0.985);
|
|
180
|
+
}
|
|
181
|
+
to {
|
|
182
|
+
opacity: 1;
|
|
183
|
+
transform: translateY(0) scale(1);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
@keyframes stalux-page-rise-in {
|
|
188
|
+
from {
|
|
189
|
+
opacity: 0;
|
|
190
|
+
transform: translateY(18px);
|
|
191
|
+
}
|
|
192
|
+
to {
|
|
193
|
+
opacity: 1;
|
|
194
|
+
transform: translateY(0);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
@keyframes stalux-page-rise-back-in {
|
|
199
|
+
from {
|
|
200
|
+
opacity: 0;
|
|
201
|
+
transform: translateY(-18px);
|
|
202
|
+
}
|
|
203
|
+
to {
|
|
204
|
+
opacity: 1;
|
|
205
|
+
transform: translateY(0);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
@keyframes stalux-page-content-in {
|
|
210
|
+
from {
|
|
211
|
+
opacity: 0;
|
|
212
|
+
transform: translateY(10px);
|
|
213
|
+
}
|
|
214
|
+
to {
|
|
215
|
+
opacity: 1;
|
|
216
|
+
transform: translateY(0);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
@keyframes stalux-page-content-back-in {
|
|
221
|
+
from {
|
|
222
|
+
opacity: 0;
|
|
223
|
+
transform: translateY(-10px);
|
|
224
|
+
}
|
|
225
|
+
to {
|
|
226
|
+
opacity: 1;
|
|
227
|
+
transform: translateY(0);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
@keyframes stalux-page-tag-cloud-in {
|
|
232
|
+
from {
|
|
233
|
+
opacity: 0;
|
|
234
|
+
transform: scale(0.985);
|
|
235
|
+
}
|
|
236
|
+
to {
|
|
237
|
+
opacity: 1;
|
|
238
|
+
transform: scale(1);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
@keyframes stalux-page-tag-cloud-back-in {
|
|
243
|
+
from {
|
|
244
|
+
opacity: 0;
|
|
245
|
+
transform: scale(1.015);
|
|
246
|
+
}
|
|
247
|
+
to {
|
|
248
|
+
opacity: 1;
|
|
249
|
+
transform: scale(1);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
@keyframes stalux-page-from-right-in {
|
|
254
|
+
from {
|
|
255
|
+
opacity: 0;
|
|
256
|
+
transform: translateX(22px);
|
|
257
|
+
}
|
|
258
|
+
to {
|
|
259
|
+
opacity: 1;
|
|
260
|
+
transform: translateX(0);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
@keyframes stalux-page-from-left-in {
|
|
265
|
+
from {
|
|
266
|
+
opacity: 0;
|
|
267
|
+
transform: translateX(-22px);
|
|
268
|
+
}
|
|
269
|
+
to {
|
|
270
|
+
opacity: 1;
|
|
271
|
+
transform: translateX(0);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
@keyframes stalux-page-article-in {
|
|
276
|
+
from {
|
|
277
|
+
opacity: 0;
|
|
278
|
+
transform: translateY(6px);
|
|
279
|
+
}
|
|
280
|
+
to {
|
|
281
|
+
opacity: 1;
|
|
282
|
+
transform: translateY(0);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
@keyframes stalux-page-article-back-in {
|
|
287
|
+
from {
|
|
288
|
+
opacity: 0;
|
|
289
|
+
transform: translateY(-6px);
|
|
290
|
+
}
|
|
291
|
+
to {
|
|
292
|
+
opacity: 1;
|
|
293
|
+
transform: translateY(0);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
@keyframes stalux-page-error-in {
|
|
298
|
+
from {
|
|
299
|
+
opacity: 0;
|
|
300
|
+
transform: scale(0.97);
|
|
301
|
+
}
|
|
302
|
+
to {
|
|
303
|
+
opacity: 1;
|
|
304
|
+
transform: scale(1);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
@keyframes stalux-page-error-back-in {
|
|
309
|
+
from {
|
|
310
|
+
opacity: 0;
|
|
311
|
+
transform: scale(0.97);
|
|
312
|
+
}
|
|
313
|
+
to {
|
|
314
|
+
opacity: 1;
|
|
315
|
+
transform: scale(1);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
@keyframes stalux-page-fade-in {
|
|
320
|
+
from {
|
|
321
|
+
opacity: 0;
|
|
322
|
+
}
|
|
323
|
+
to {
|
|
324
|
+
opacity: 1;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
@keyframes stalux-page-fade-back-in {
|
|
329
|
+
from {
|
|
330
|
+
opacity: 0;
|
|
331
|
+
}
|
|
332
|
+
to {
|
|
333
|
+
opacity: 1;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
::view-transition-group(stalux-page) {
|
|
338
|
+
z-index: 1;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
::view-transition-old(stalux-page),
|
|
342
|
+
::view-transition-new(stalux-page) {
|
|
343
|
+
transform-origin: center top;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
@media (prefers-reduced-motion: reduce) {
|
|
347
|
+
::view-transition-group(stalux-page),
|
|
348
|
+
::view-transition-old(stalux-page),
|
|
349
|
+
::view-transition-new(stalux-page) {
|
|
350
|
+
animation: none !important;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.stalux-main {
|
|
354
|
+
animation: none !important;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
export type RouteKind =
|
|
2
|
+
| "home"
|
|
3
|
+
| "archive"
|
|
4
|
+
| "links"
|
|
5
|
+
| "words"
|
|
6
|
+
| "about"
|
|
7
|
+
| "tags-index"
|
|
8
|
+
| "tags-detail"
|
|
9
|
+
| "categories-index"
|
|
10
|
+
| "categories-detail"
|
|
11
|
+
| "article"
|
|
12
|
+
| "not-found"
|
|
13
|
+
| "other";
|
|
14
|
+
|
|
15
|
+
export type TransitionAnimation = {
|
|
16
|
+
name: string;
|
|
17
|
+
delay?: number | string;
|
|
18
|
+
duration?: number | string;
|
|
19
|
+
easing?: string;
|
|
20
|
+
fillMode?: string;
|
|
21
|
+
direction?: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type SidebarSide = "left" | "right";
|
|
25
|
+
|
|
26
|
+
export type RouteAnimation = {
|
|
27
|
+
forwards: {
|
|
28
|
+
old: TransitionAnimation | TransitionAnimation[];
|
|
29
|
+
new: TransitionAnimation | TransitionAnimation[];
|
|
30
|
+
};
|
|
31
|
+
backwards: {
|
|
32
|
+
old: TransitionAnimation | TransitionAnimation[];
|
|
33
|
+
new: TransitionAnimation | TransitionAnimation[];
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const easing = "cubic-bezier(0.25, 0.9, 0.2, 1)";
|
|
38
|
+
const exitAnimation: TransitionAnimation = {
|
|
39
|
+
name: "stalux-page-fade-out",
|
|
40
|
+
duration: "0.32s",
|
|
41
|
+
easing,
|
|
42
|
+
fillMode: "both",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function createRouteAnimation(name: string, backName = `${name}-back`): RouteAnimation {
|
|
46
|
+
return {
|
|
47
|
+
forwards: {
|
|
48
|
+
old: exitAnimation,
|
|
49
|
+
new: {
|
|
50
|
+
name: `stalux-page-${name}-in`,
|
|
51
|
+
duration: "0.42s",
|
|
52
|
+
easing,
|
|
53
|
+
fillMode: "both",
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
backwards: {
|
|
57
|
+
old: exitAnimation,
|
|
58
|
+
new: {
|
|
59
|
+
name: `stalux-page-${backName}-in`,
|
|
60
|
+
duration: "0.42s",
|
|
61
|
+
easing,
|
|
62
|
+
fillMode: "both",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function normalizedPath(pathname: string): string {
|
|
69
|
+
const path = pathname.replace(/\/+$/, "");
|
|
70
|
+
return path || "/";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function getRouteKind(pathname: string): RouteKind {
|
|
74
|
+
const path = normalizedPath(pathname);
|
|
75
|
+
|
|
76
|
+
if (path === "/") return "home";
|
|
77
|
+
if (path === "/archives") return "archive";
|
|
78
|
+
if (path === "/links") return "links";
|
|
79
|
+
if (path === "/words") return "words";
|
|
80
|
+
if (path === "/about") return "about";
|
|
81
|
+
if (path === "/404") return "not-found";
|
|
82
|
+
if (path === "/tags") return "tags-index";
|
|
83
|
+
if (path.startsWith("/tags/")) return "tags-detail";
|
|
84
|
+
if (path === "/categories") return "categories-index";
|
|
85
|
+
if (path.startsWith("/categories/")) return "categories-detail";
|
|
86
|
+
if (path.startsWith("/posts/")) return "article";
|
|
87
|
+
|
|
88
|
+
return "other";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const routeAnimations: Record<RouteKind, RouteAnimation> = {
|
|
92
|
+
home: createRouteAnimation("home", "home-back"),
|
|
93
|
+
archive: createRouteAnimation("vertical", "vertical-back"),
|
|
94
|
+
links: createRouteAnimation("grid", "grid-back"),
|
|
95
|
+
words: createRouteAnimation("rise", "rise-back"),
|
|
96
|
+
about: createRouteAnimation("content", "content-back"),
|
|
97
|
+
"tags-index": createRouteAnimation("tag-cloud", "tag-cloud-back"),
|
|
98
|
+
"tags-detail": createRouteAnimation("from-right", "from-left"),
|
|
99
|
+
"categories-index": createRouteAnimation("category-grid", "category-grid-back"),
|
|
100
|
+
"categories-detail": createRouteAnimation("from-left", "from-right"),
|
|
101
|
+
article: createRouteAnimation("article", "article-back"),
|
|
102
|
+
"not-found": createRouteAnimation("error", "error-back"),
|
|
103
|
+
other: createRouteAnimation("fade", "fade-back"),
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export function getRouteAnimation(routeKind: RouteKind): RouteAnimation {
|
|
107
|
+
return routeAnimations[routeKind];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function createSidebarAnimation(
|
|
111
|
+
side: SidebarSide,
|
|
112
|
+
phase: "in" | "out",
|
|
113
|
+
backwards: boolean,
|
|
114
|
+
): TransitionAnimation {
|
|
115
|
+
return {
|
|
116
|
+
name: `stalux-sidebar-${side}-${backwards ? "back-" : ""}${phase}`,
|
|
117
|
+
duration: "0.8s",
|
|
118
|
+
easing,
|
|
119
|
+
fillMode: "both",
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function getSidebarAnimation(side: SidebarSide): RouteAnimation {
|
|
124
|
+
return {
|
|
125
|
+
forwards: {
|
|
126
|
+
old: createSidebarAnimation(side, "out", false),
|
|
127
|
+
new: createSidebarAnimation(side, "in", false),
|
|
128
|
+
},
|
|
129
|
+
backwards: {
|
|
130
|
+
old: createSidebarAnimation(side, "out", true),
|
|
131
|
+
new: createSidebarAnimation(side, "in", true),
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|