@xingwangzhe/stalux 1.47.1 → 1.49.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.
Files changed (78) hide show
  1. package/README.md +7 -2
  2. package/package.json +1 -1
  3. package/public/background/pattern-1.min.dark.webp +0 -0
  4. package/public/background/pattern-10.min.dark.webp +0 -0
  5. package/public/background/pattern-11.min.dark.webp +0 -0
  6. package/public/background/pattern-12.min.dark.webp +0 -0
  7. package/public/background/pattern-13.min.dark.webp +0 -0
  8. package/public/background/pattern-14.min.dark.webp +0 -0
  9. package/public/background/pattern-15.min.dark.webp +0 -0
  10. package/public/background/pattern-16.min.dark.webp +0 -0
  11. package/public/background/pattern-17.min.dark.webp +0 -0
  12. package/public/background/pattern-18.min.dark.webp +0 -0
  13. package/public/background/pattern-19.min.dark.webp +0 -0
  14. package/public/background/pattern-2.min.dark.webp +0 -0
  15. package/public/background/pattern-20.min.dark.webp +0 -0
  16. package/public/background/pattern-21.min.dark.webp +0 -0
  17. package/public/background/pattern-22.min.dark.webp +0 -0
  18. package/public/background/pattern-23.min.dark.webp +0 -0
  19. package/public/background/pattern-24.min.dark.webp +0 -0
  20. package/public/background/pattern-25.min.dark.webp +0 -0
  21. package/public/background/pattern-26.min.dark.webp +0 -0
  22. package/public/background/pattern-27.min.dark.webp +0 -0
  23. package/public/background/pattern-28.min.dark.webp +0 -0
  24. package/public/background/pattern-29.min.dark.webp +0 -0
  25. package/public/background/pattern-3.min.dark.webp +0 -0
  26. package/public/background/pattern-30.min.dark.webp +0 -0
  27. package/public/background/pattern-31.min.dark.webp +0 -0
  28. package/public/background/pattern-32.min.dark.webp +0 -0
  29. package/public/background/pattern-33.min.dark.webp +0 -0
  30. package/public/background/pattern-34.min.dark.webp +0 -0
  31. package/public/background/pattern-35.min.dark.webp +0 -0
  32. package/public/background/pattern-36.min.dark.webp +0 -0
  33. package/public/background/pattern-37.min.dark.webp +0 -0
  34. package/public/background/pattern-38.min.dark.webp +0 -0
  35. package/public/background/pattern-39.min.dark.webp +0 -0
  36. package/public/background/pattern-4.min.dark.webp +0 -0
  37. package/public/background/pattern-40.min.dark.webp +0 -0
  38. package/public/background/pattern-41.min.dark.webp +0 -0
  39. package/public/background/pattern-42.min.dark.webp +0 -0
  40. package/public/background/pattern-5.min.dark.webp +0 -0
  41. package/public/background/pattern-6.min.dark.webp +0 -0
  42. package/public/background/pattern-7.min.dark.webp +0 -0
  43. package/public/background/pattern-8.min.dark.webp +0 -0
  44. package/public/background/pattern-9.min.dark.webp +0 -0
  45. package/src/components/stalux/common/Weather.astro +29 -2
  46. package/src/components/stalux/common/author.astro +1 -0
  47. package/src/components/stalux/common/date.astro +4 -0
  48. package/src/components/stalux/footer/FooterBadges.astro +14 -3
  49. package/src/components/stalux/layout/footer.astro +1 -12
  50. package/src/components/stalux/layout/head.astro +1 -1
  51. package/src/components/stalux/links/linkCard.astro +5 -1
  52. package/src/components/stalux/links/linkList.astro +2 -0
  53. package/src/components/stalux/links/socialMedia.astro +1 -0
  54. package/src/components/stalux/posts/postCard.astro +1 -0
  55. package/src/components/stalux/posts/postContent.astro +2 -0
  56. package/src/index.ts +3 -0
  57. package/src/layouts/Stalux.astro +0 -1
  58. package/src/pages/404.astro +1 -0
  59. package/src/pages/index.astro +1 -0
  60. package/src/pages/stalux-assets/badges/[id].svg.ts +20 -0
  61. package/src/plugins/image-dimensions.ts +34 -0
  62. package/src/scripts/analytics-runtime.ts +103 -4
  63. package/src/scripts/background.ts +10 -3
  64. package/src/scripts/core-runtime.ts +0 -1
  65. package/src/scripts/link-icons.ts +54 -0
  66. package/src/scripts/photoswipe.ts +4 -3
  67. package/src/scripts/random-posts.ts +27 -1
  68. package/src/scripts/search-dialog.ts +1 -0
  69. package/src/scripts/swup-router.ts +2 -0
  70. package/src/styles/generated-author.css +3 -0
  71. package/src/styles/generated-cards.css +3 -0
  72. package/src/styles/generated-home.css +3 -0
  73. package/src/styles/generated-links.css +1 -1
  74. package/src/styles/generated-not-found.css +3 -0
  75. package/src/styles/generated-social.css +3 -0
  76. package/src/styles/generated.css +1 -1
  77. package/src/utils/badge-generator.ts +33 -5
  78. package/src/utils/route-catalog.ts +4 -0
package/README.md CHANGED
@@ -14,7 +14,9 @@ All HTML routes include a circular back-to-top button in the footer. It appears
14
14
 
15
15
  The homepage typewriter is built into Stalux and continues to use the `items` string list in `stalux/config/typetexts.yml`, with no new configuration. Defaults are 100ms per typed grapheme, 33ms per deleted grapheme, a 1500ms hold, and a 500ms pause after deletion. It pauses outside the viewport or in background tabs and cleans up on soft navigation. Reduced motion displays whole messages without a blinking cursor. Without JavaScript, the first message remains visible; screen readers and print receive the complete list.
16
16
 
17
- Standalone images written as `![Image description](url)` receive a `figure` and `figcaption` through a build-time satteri HAST plugin. Captions appear as small, centered gray text below the image, preserving image alt text and the PhotoSwipe lightbox. No configuration or browser script is required. Empty descriptions, inline images, multiple images in one paragraph, and existing figures are left unchanged. Captions are plain text with automatic escaping.
17
+ Standalone images written as `![Image description](url)` receive a `figure` and `figcaption` through a build-time satteri HAST plugin. Captions appear as small, centered gray text below the image, preserving image alt text and the PhotoSwipe lightbox. No configuration or browser script is required. Empty descriptions, inline images, multiple images in one paragraph, and existing figures are left unchanged. Captions are plain text with automatic escaping. External images can declare intrinsic size in the markdown title: `![description](https://example.com/a.webp "1150x439")`. The build turns it into `width`/`height`, removes the tooltip title, and gives PhotoSwipe matching `data-pswp-width/height`; images without a size declaration keep their previous output.
18
+
19
+ Friend-link icons are requested only when they approach the viewport (400px prefetch margin), instead of the browser's large native lazy window pulling every third-party avatar on the page. A failed icon keeps a placeholder disc and never affects the text content. The Google Analytics SDK loads after the first screen settles (first input, 600ms of stable LCP, or six seconds after parsing), while `page_view` snapshots and the dataLayer queue are still written during navigation without waiting for the SDK. The random-posts sidebar likewise requests its index only near the viewport.
18
20
 
19
21
  Code blocks default to the single `dark-plus` theme, with system color-scheme switching and theme selectors disabled. To opt into another theme or switching behavior, explicitly override `themes`, `useDarkModeMediaQuery`, and `themeCssSelector` through `expressiveCode`. This only changes generated code styles and adds no telemetry.
20
22
 
@@ -138,6 +140,9 @@ Processed pages are cached under `node_modules/.astro/stalux-page-output/`. The
138
140
 
139
141
  The desktop tag cloud loads dynamically near the viewport. Switching to mobile, leaving the viewport or hiding the document destroys it and stops animation scheduling; becoming visible recreates it. Tag clicks use the same Swup navigation as ordinary internal links. Clocks and the footer runtime update only while visible, and the date changes only on a new day. Comments load within half a viewport (capped at 400px). Tag groups, archive months and category cards retain static HTML while deferring offscreen rendering.
140
142
 
143
+
144
+ Configured footer badges are generated as content-addressed SVG assets. Native lazy loading keeps closed groups from embedding their image payload in every page; expanding groups also works without JavaScript. Custom component badges outside footer.yml retain Data URL support.
145
+
141
146
  ### Modern JavaScript
142
147
 
143
148
  TypeScript uses ESNext. The theme defaults `vite.build.target` to `"esnext"` to retain modern syntax with minimal compatibility transforms; an explicit consumer target takes precedence. This targets modern browsers, does not polyfill missing Web APIs, and does not move ordinary scripts onto the GPU. Biome enforces `noVar` and `useConst` as errors in maintained source; dependencies and generated bundles are outside this source policy.
@@ -381,4 +386,4 @@ Local covers are read from the final `publicDir`. Remote covers are requested by
381
386
 
382
387
  Motion is specified for 55 visible semantic modules in [Modules and view transitions](docs/MOTION.md). Swup animates one visible, height-bounded heading or introductory region after destination assets are ready, with cues for reading, returning, filtering and previous/next articles. Long documents stay readable and the header, footer and dark canvas remain stable. Phones use a short fade; history restores the reading position; same-page anchors use interruptible native scrolling. Module hover, focus, press and asynchronous update feedback complement bounded secondary entry cues. Reduced motion and print remain static and readable. No new configuration is required.
383
388
 
384
- Built-in background artwork uses committed lossless WebP companions. Ratio-only SVG sizing is preserved with `contain`; custom background URLs retain their existing sizing. Regenerate companions after changing the SVG originals with `bun run backgrounds:build`.
389
+ Built-in backgrounds use committed WebP companions composited against the default #202020 canvas (quality 80, original resolution). Custom CSS canvas colors use the original transparent WebP; SVG sources are retained. Ratio-only SVG sizing is preserved with `contain`; custom background URLs retain their existing sizing. Regenerate companions after changing the SVG originals with `bun run backgrounds:build`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xingwangzhe/stalux",
3
- "version": "1.47.1",
3
+ "version": "1.49.0",
4
4
  "description": "A powerful, modern Astro blog theme — use as template or install as plugin",
5
5
  "keywords": [
6
6
  "astro",
@@ -48,9 +48,36 @@ const zh = (site.lang || "zh-CN").startsWith("zh");
48
48
  )}
49
49
  <script>
50
50
  import { registerPageLifecycle } from "../../../scripts/page-runtime";
51
- import { mountWeather } from "../../../scripts/weather";
52
51
 
53
- registerPageLifecycle("weather", mountWeather);
52
+ // 页脚天气在首屏之外。模块本身、以及它发起的两次第三方请求,
53
+ // 都推迟到卡片接近视口时才发生;SSR 已经输出占位文案,不依赖这段脚本。
54
+ registerPageLifecycle("weather", () => {
55
+ const targets = [...document.querySelectorAll<HTMLElement>("[data-stalux-weather]")];
56
+ if (targets.length === 0) return;
57
+ let disposed = false;
58
+ let cleanup: (() => void) | undefined;
59
+ const start = async () => {
60
+ observer?.disconnect();
61
+ const { mountWeather } = await import("../../../scripts/weather");
62
+ if (disposed) return;
63
+ cleanup = mountWeather();
64
+ };
65
+ const observer = globalThis.IntersectionObserver
66
+ ? new IntersectionObserver(
67
+ (entries) => {
68
+ if (entries.some((entry) => entry.isIntersecting)) void start();
69
+ },
70
+ { rootMargin: "200px" },
71
+ )
72
+ : undefined;
73
+ if (observer) for (const target of targets) observer.observe(target);
74
+ else void start();
75
+ return () => {
76
+ disposed = true;
77
+ observer?.disconnect();
78
+ cleanup?.();
79
+ };
80
+ });
54
81
  </script>
55
82
  <style>
56
83
  .stalux-weather {
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import "../../../styles/generated-author.css";
2
3
  import { Image } from "astro:assets";
3
4
  import { getCollection } from "astro:content";
4
5
 
@@ -23,3 +23,7 @@
23
23
  timeElement.dateTime = timeElement.textContent;
24
24
  })();
25
25
  </script>
26
+
27
+ <script>
28
+ import "../../../scripts/date-time";
29
+ </script>
@@ -1,9 +1,16 @@
1
1
  ---
2
2
  import { getCollection } from "astro:content";
3
3
  import type { BadgeItem, BadgeOptions } from "@utils/badge-generator";
4
- import { generateBadge, isBadgeGroup, svgToDataUrl } from "@utils/badge-generator";
5
- import { getSiteData } from "@utils/config-utils";
4
+ import {
5
+ badgeAssetId,
6
+ createBadgeAssets,
7
+ generateBadge,
8
+ isBadgeGroup,
9
+ svgToDataUrl,
10
+ } from "@utils/badge-generator";
11
+ import { getFooterData, getSiteData } from "@utils/config-utils";
6
12
  import { createTranslator } from "@utils/i18n";
13
+ import { sitePath } from "@utils/site-path";
7
14
 
8
15
  interface Props {
9
16
  badges?: BadgeItem[];
@@ -12,11 +19,15 @@ interface Props {
12
19
  const { badges } = Astro.props;
13
20
  const configEntries = await getCollection("config");
14
21
  const siteConfig = getSiteData(configEntries);
22
+ const badgeAssets = createBadgeAssets(getFooterData(configEntries)?.badges);
15
23
  const { t } = createTranslator(siteConfig.lang || "zh-CN");
16
24
 
17
25
  function badgeImg(opts: BadgeOptions) {
18
26
  const svg = generateBadge(opts, Astro.logger);
19
- const src = svgToDataUrl(svg);
27
+ const id = badgeAssetId(svg);
28
+ const src = badgeAssets.has(id)
29
+ ? sitePath(`stalux-assets/badges/${id}.svg`)
30
+ : svgToDataUrl(svg);
20
31
  const alt = opts.alt || `${opts.label}: ${opts.message}`;
21
32
  const width = svg.match(/<svg\b[^>]*\bwidth="(\d+)"/)?.[1];
22
33
  const height = svg.match(/<svg\b[^>]*\bheight="(\d+)"/)?.[1];
@@ -6,7 +6,7 @@ import FooterBeian from "@components/stalux/footer/FooterBeian.astro";
6
6
  import FooterCopyright from "@components/stalux/footer/FooterCopyright.astro";
7
7
  import FooterStats from "@components/stalux/footer/FooterStats.astro";
8
8
  import FooterThemeInfo from "@components/stalux/footer/FooterThemeInfo.astro";
9
- import type { BadgeItem, BadgeOptions } from "@utils/badge-generator";
9
+ import { type BadgeItem, normalizeBadgeStyle } from "@utils/badge-generator";
10
10
  import { getAuthorData, getFooterData, getSiteData } from "@utils/config-utils";
11
11
  import { formatWordCount, getTotalWordCount } from "@utils/word-count-utils";
12
12
  import BackToTop from "./BackToTop.astro";
@@ -19,17 +19,6 @@ const authorConfig = getAuthorData(configEntries);
19
19
  const author = authorConfig.name;
20
20
 
21
21
  // 标准化 badges(扁平 badge 或 分组),确保 style 字段合法
22
- const badgeStyles = new Set<BadgeOptions["style"]>([
23
- "flat",
24
- "plastic",
25
- "flat-square",
26
- "for-the-badge",
27
- "social",
28
- ]);
29
- const normalizeBadgeStyle = (style: string | undefined): BadgeOptions["style"] =>
30
- style && badgeStyles.has(style as BadgeOptions["style"])
31
- ? (style as BadgeOptions["style"])
32
- : "flat";
33
22
  const badges: BadgeItem[] | undefined = footerConfig?.badges?.map((item) => {
34
23
  if ("title" in item) {
35
24
  return {
@@ -77,7 +77,7 @@ const ldJson = JSON.stringify(seo.schema).replace(/<\//g, "<\\/");
77
77
  )}
78
78
  <!-- 代码字体由 Astro Fonts API 注入;正文按页面生成 native CJK 子集。 -->
79
79
  <Font cssVariable="--font-code" />
80
- <link rel="prefetch" as="style" href={pagefindStyles} data-stalux-search-style>
80
+ <link rel="stylesheet" data-href={pagefindStyles} data-stalux-search-style>
81
81
  <noscript><link rel="stylesheet" href={pagefindStyles}></noscript>
82
82
  {headConfig?.anyhead && <Fragment set:html={headConfig.anyhead} />}
83
83
  {headConfig?.googleAnalyticsId && <GoogleAnalytics />}
@@ -39,9 +39,13 @@ const icon = link.icon ?? link.avatar ?? "/favicon.ico";
39
39
  <div class="stalux-link-card">
40
40
  <div class="stalux-link-card-avatar">
41
41
  <img
42
- src={icon}
42
+ src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
43
+ data-src={icon}
44
+ data-stalux-link-icon
43
45
  alt={t("links.iconAlt", { name: title })}
44
46
  loading="lazy"
47
+ decoding="async"
48
+ fetchpriority="low"
45
49
  width="80"
46
50
  height="80"
47
51
  >
@@ -17,3 +17,5 @@ const links = linksConfig?.sites ?? [];
17
17
  </li>
18
18
  ))}
19
19
  </ul>
20
+
21
+ <script src="../../../scripts/link-icons.ts"></script>
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import "../../../styles/generated-social.css";
2
3
  // 按需导入图标 — Vite tree-shake 只打包用到的图标
3
4
 
4
5
  import { getCollection } from "astro:content";
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import "../../../styles/generated-cards.css";
2
3
  import type { CollectionEntry } from "astro:content";
3
4
  import { getCollection } from "astro:content";
4
5
 
@@ -128,6 +128,7 @@ const mdSourceUrl = exportMd ? `${Astro.url.pathname.replace(/\/$/, "")}.md` : "
128
128
  <!-- Article Body -->
129
129
  <section
130
130
  class="stalux-post-body stalux-prose"
131
+ data-stalux-gallery-body
131
132
  data-stalux-motion-content
132
133
  data-pagefind-body
133
134
  data-stalux-post-id={props.postId === undefined ? undefined : String(props.postId)}
@@ -138,6 +139,7 @@ const mdSourceUrl = exportMd ? `${Astro.url.pathname.replace(/\/$/, "")}.md` : "
138
139
  </article>
139
140
 
140
141
  <script>
142
+ import "../../../scripts/module-motion";
141
143
  import "../../../scripts/photoswipe";
142
144
  import { mountCodeAccessibility } from "../../../scripts/code-accessibility";
143
145
  import { registerPageLifecycle } from "../../../scripts/page-runtime";
package/src/index.ts CHANGED
@@ -64,6 +64,7 @@ import { createViteAliases } from "./internal/vite-aliases";
64
64
  import { webMcpMutationsPlugin } from "./internal/webmcp-mutations";
65
65
  import { featureFlagsHast, featureFlagsMdast } from "./plugins/feature-flags";
66
66
  import { imageCaptions } from "./plugins/image-captions";
67
+ import { imageDimensions } from "./plugins/image-dimensions";
67
68
  import { temml } from "./plugins/satteri-temml";
68
69
  import { describeError } from "./utils/diagnostics";
69
70
 
@@ -293,6 +294,8 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
293
294
  }),
294
295
  seen,
295
296
  );
297
+ // Dimensions must land before PhotoSwipe copies them onto the link.
298
+ appendUniquePlugin(processorOptions.hastPlugins, imageDimensions, seen);
296
299
  appendUniquePlugin(processorOptions.hastPlugins, photoswipe(), seen);
297
300
  appendUniquePlugin(processorOptions.hastPlugins, imageCaptions, seen);
298
301
  appendUniquePlugin(processorOptions.hastPlugins, featureFlagsHast, seen);
@@ -74,7 +74,6 @@ const routeKind = getRouteKind(Astro.url.pathname);
74
74
  />
75
75
  <script>
76
76
  import "../scripts/background.ts";
77
- import "../scripts/module-motion.ts";
78
77
  import "../scripts/view-transitions.ts";
79
78
  // Every page type has at least one list, so the unroll cue is registered
80
79
  // once in the shared layout instead of per page. Page-runtime dedupes it
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import "../styles/generated-not-found.css";
2
3
  import { getCollection } from "astro:content";
3
4
  import Stalux from "@layouts/Stalux.astro";
4
5
  import { getSiteData } from "@utils/config-utils";
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import "../styles/generated-home.css";
2
3
  import { getCollection } from "astro:content";
3
4
  import { createTranslator } from "@utils/i18n";
4
5
 
@@ -0,0 +1,20 @@
1
+ import { getCollection } from "astro:content";
2
+ import type { APIRoute } from "astro";
3
+ import { createBadgeAssets } from "../../../utils/badge-generator";
4
+ import { getFooterData } from "../../../utils/config-utils";
5
+
6
+ export const prerender = true;
7
+
8
+ export async function getStaticPaths() {
9
+ const footer = getFooterData(await getCollection("config"));
10
+ const assets = createBadgeAssets(footer?.badges);
11
+ return [...assets].map(([id, svg]) => ({ params: { id }, props: { svg } }));
12
+ }
13
+
14
+ export const GET: APIRoute = ({ props }) =>
15
+ new Response(props.svg, {
16
+ headers: {
17
+ "Content-Type": "image/svg+xml; charset=utf-8",
18
+ "Cache-Control": "public, max-age=31536000, immutable",
19
+ },
20
+ });
@@ -0,0 +1,34 @@
1
+ import { defineHastPlugin } from "satteri";
2
+
3
+ /**
4
+ * Markdown 图片标题里的 `宽x高` 是外部图片的尺寸声明,不是给读者看的提示。
5
+ *
6
+ * 外部图片无法在构建期读取像素尺寸,浏览器只能等下载完才知道比例,正文会被
7
+ * 推开(CLS)。写成 `![alt](https://… "532x200")` 后,这里在 HAST 阶段补上
8
+ * width/height,PhotoSwipe 也能拿到 data-pswp-width/height;标题本身随即移除。
9
+ */
10
+ const DIMENSION_TITLE = /^(\d{1,6})\s*[x×]\s*(\d{1,6})$/u;
11
+
12
+ export const imageDimensions = defineHastPlugin({
13
+ name: "stalux-image-dimensions",
14
+ element: {
15
+ filter: ["img"],
16
+ visit(node, ctx) {
17
+ const title = node.properties?.title;
18
+ if (typeof title !== "string") return;
19
+ const match = DIMENSION_TITLE.exec(title.trim());
20
+ if (!match) return;
21
+ const width = Number(match[1]);
22
+ const height = Number(match[2]);
23
+ if (!(width > 0 && height > 0)) return;
24
+ if (node.properties?.width === undefined) {
25
+ ctx.setProperty(node, "width", String(width));
26
+ }
27
+ if (node.properties?.height === undefined) {
28
+ ctx.setProperty(node, "height", String(height));
29
+ }
30
+ // 尺寸声明已转成属性,不再让浏览器把它当成悬浮提示。
31
+ ctx.setProperty(node, "title", undefined);
32
+ },
33
+ },
34
+ });
@@ -202,6 +202,105 @@ function initializeClarity(id: string): void {
202
202
  });
203
203
  }
204
204
 
205
+ /**
206
+ * GA's SDK is 195 KB compressed / ~610 KB parsed, larger than every first-party
207
+ * asset combined. The dataLayer queue and the page_view snapshot do not need it:
208
+ * events are pushed immediately and consumed when the SDK finally arrives.
209
+ * Loading is gated on the same signals that mean "first paint is done" instead
210
+ * of `window.load`, which slow third-party resources can postpone forever.
211
+ */
212
+ const GOOGLE_SDK_FLOOR_MS = 1200;
213
+ const GOOGLE_SDK_LCP_QUIET_MS = 600;
214
+ const GOOGLE_SDK_CEILING_MS = 6000;
215
+ let googleSdkStarted = false;
216
+ let googleGateInstalled = false;
217
+ let googleGateOpen = false;
218
+
219
+ function runBackgroundTask(callback: () => void): void {
220
+ const scheduler = (
221
+ window as Window & {
222
+ scheduler?: {
223
+ postTask: (
224
+ task: () => void,
225
+ options: { priority: "background" },
226
+ ) => Promise<unknown>;
227
+ };
228
+ }
229
+ ).scheduler;
230
+ if (scheduler?.postTask) {
231
+ void scheduler
232
+ .postTask(callback, { priority: "background" })
233
+ .catch(() => setTimeout(callback, 0));
234
+ return;
235
+ }
236
+ setTimeout(callback, 0);
237
+ }
238
+
239
+ function startGoogleSdk(): void {
240
+ if (googleSdkStarted || !state.googleId) return;
241
+ googleSdkStarted = true;
242
+ void loadTrackerScript(
243
+ `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(state.googleId)}`,
244
+ { "data-stalux-google": "true" },
245
+ ).catch(() => logger.debug("Google SDK unavailable; queued events retained"));
246
+ }
247
+
248
+ /** Three independent doors: first input, LCP quiet, and a hard ceiling after parsing. */
249
+ function installGoogleGate(): void {
250
+ if (googleGateInstalled) return;
251
+ googleGateInstalled = true;
252
+ const startedAt = performance.now();
253
+ const floorAt = startedAt + GOOGLE_SDK_FLOOR_MS;
254
+ const ceilingAt = startedAt + GOOGLE_SDK_CEILING_MS;
255
+ let quietTimer: ReturnType<typeof setTimeout> | undefined;
256
+ let ceilingTimer: ReturnType<typeof setTimeout> | undefined;
257
+ let observer: PerformanceObserver | undefined;
258
+
259
+ const cleanup = () => {
260
+ for (const type of ["pointerdown", "keydown", "wheel", "touchstart"]) {
261
+ window.removeEventListener(type, handleInteraction);
262
+ }
263
+ if (quietTimer !== undefined) clearTimeout(quietTimer);
264
+ if (ceilingTimer !== undefined) clearTimeout(ceilingTimer);
265
+ observer?.disconnect();
266
+ observer = undefined;
267
+ };
268
+ const open = () => {
269
+ if (googleGateOpen) return;
270
+ googleGateOpen = true;
271
+ cleanup();
272
+ // Never compete with the input that just opened the gate.
273
+ runBackgroundTask(startGoogleSdk);
274
+ };
275
+ function handleInteraction(): void {
276
+ open();
277
+ }
278
+ const scheduleQuiet = () => {
279
+ if (googleGateOpen) return;
280
+ if (quietTimer !== undefined) clearTimeout(quietTimer);
281
+ const wait = Math.max(GOOGLE_SDK_LCP_QUIET_MS, floorAt - performance.now());
282
+ quietTimer = setTimeout(() => {
283
+ quietTimer = undefined;
284
+ if (performance.now() >= floorAt) open();
285
+ else scheduleQuiet();
286
+ }, wait);
287
+ };
288
+
289
+ if ("PerformanceObserver" in window) {
290
+ try {
291
+ observer = new window.PerformanceObserver(scheduleQuiet);
292
+ observer.observe({ type: "largest-contentful-paint", buffered: true });
293
+ } catch {
294
+ // Older engines may know the constructor but not this entry type.
295
+ }
296
+ }
297
+ for (const type of ["pointerdown", "keydown", "wheel", "touchstart"]) {
298
+ window.addEventListener(type, handleInteraction, { passive: true, once: true });
299
+ }
300
+ ceilingTimer = setTimeout(open, Math.max(0, ceilingAt - startedAt));
301
+ if (document.hidden) open();
302
+ }
303
+
205
304
  function initializeTrackers(): void {
206
305
  const config = document.body?.dataset;
207
306
  if (!config) return;
@@ -218,10 +317,10 @@ function initializeTrackers(): void {
218
317
  host.gtag("js", new Date());
219
318
  host.gtag("config", state.googleId, { send_page_view: false });
220
319
  }
221
- void loadTrackerScript(
222
- `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(state.googleId)}`,
223
- { "data-stalux-google": "true" },
224
- ).catch(() => logger.debug("Google SDK unavailable; queued events retained"));
320
+ installGoogleGate();
321
+ // A navigation can expose the measurement ID after the first document
322
+ // already opened its gate; in that case load immediately.
323
+ if (googleGateOpen) startGoogleSdk();
225
324
  }
226
325
  if (config.staluxClarityId) initializeClarity(config.staluxClarityId);
227
326
  }
@@ -2,7 +2,7 @@
2
2
  * 每个完整文档只随机选择一次的会话背景。
3
3
  *
4
4
  * SSR 保持确定的深色底和原有双层 DOM,不内联背景 URL。文档解析完成即
5
- * 低优先级请求并异步解码一张无损 WebP,无需等待 window.load。首次成功时从暗底
5
+ * 低优先级请求并异步解码一张深色底 WebP,无需等待 window.load。首次成功时从暗底
6
6
  * 用 200ms opacity 淡显;减少动态效果时直接显示。解码慢或失败仍保持暗底。
7
7
  * Swup 只替换 main,背景沿用同一张,不按软导航重选/解码或再次交叉淡入。
8
8
  * 自定义 __STALUX_BG_URLS__ 继续生效;saveData 下保留暗底,不请求装饰图片。
@@ -20,7 +20,7 @@ declare global {
20
20
 
21
21
  const BACKGROUND_URLS: string[] = Array.from(
22
22
  { length: 42 },
23
- (_, i) => `/background/pattern-${i + 1}.min.webp`,
23
+ (_, i) => `/background/pattern-${i + 1}.min.dark.webp`,
24
24
  );
25
25
 
26
26
  /** Install once per full document; this module is also harmless during SSR. */
@@ -40,7 +40,14 @@ export function installSessionBackground(): void {
40
40
  ? window.__STALUX_BG_URLS__
41
41
  : BACKGROUND_URLS;
42
42
  const index = Math.floor(Math.random() * urls.length);
43
- const source = urls[index];
43
+ // The compact raster is composited against the standard canvas. Preserve
44
+ // CSS custom canvas colors with the original transparent raster.
45
+ const standardCanvas =
46
+ typeof getComputedStyle === "undefined" ||
47
+ getComputedStyle(a).backgroundColor === "rgb(32, 32, 32)";
48
+ const selected = urls[index];
49
+ const source =
50
+ custom || standardCanvas ? selected : selected?.replace(/\.dark\.webp$/u, ".webp");
44
51
  if (!source) return;
45
52
  const image = new Image();
46
53
  image.decoding = "async";
@@ -1,6 +1,5 @@
1
1
  // One shared entry for tiny, dependency-free behavior present across Stalux pages.
2
2
  // Each module registers a no-op-safe mount with page-runtime.
3
- import "./date-time";
4
3
  import "./footer-badges";
5
4
  import "./navigation";
6
5
  import "./runtime-counter";
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Link-page icons come from arbitrary third-party hosts. Native lazy loading
3
+ * still fetches everything within the browser's large lazy threshold (up to
4
+ * 3000px), and a single link list therefore pulled several megabytes of
5
+ * avatars that the reader never reached. Load only icons near the viewport.
6
+ */
7
+ import { createClientLogger } from "./logger";
8
+ import { registerPageLifecycle } from "./page-runtime";
9
+
10
+ const logger = createClientLogger("link-icons");
11
+
12
+ function reveal(image: HTMLImageElement): void {
13
+ const source = image.dataset.src;
14
+ if (!source) return;
15
+ delete image.dataset.src;
16
+ image.addEventListener(
17
+ "error",
18
+ () => {
19
+ // Keep the reserved box; the stylesheet draws a neutral disc instead.
20
+ image.removeAttribute("src");
21
+ image.dataset.staluxLinkIconFailed = "true";
22
+ logger.debug("link icon failed to load; placeholder retained");
23
+ },
24
+ { once: true },
25
+ );
26
+ image.src = source;
27
+ }
28
+
29
+ export function mountLinkIcons(): (() => void) | undefined {
30
+ const images = Array.from(
31
+ document.querySelectorAll<HTMLImageElement>("img[data-stalux-link-icon]"),
32
+ );
33
+ if (!images.length) return;
34
+
35
+ if (!("IntersectionObserver" in window)) {
36
+ for (const image of images) reveal(image);
37
+ return;
38
+ }
39
+
40
+ const observer = new IntersectionObserver(
41
+ (entries) => {
42
+ for (const entry of entries) {
43
+ if (!entry.isIntersecting) continue;
44
+ observer.unobserve(entry.target);
45
+ reveal(entry.target as HTMLImageElement);
46
+ }
47
+ },
48
+ { rootMargin: "400px 0px" },
49
+ );
50
+ for (const image of images) observer.observe(image);
51
+ return () => observer.disconnect();
52
+ }
53
+
54
+ registerPageLifecycle("link-icons", mountLinkIcons);
@@ -6,6 +6,9 @@ import { createRetryableInitializer } from "./retryable-initializer";
6
6
  const logger = createClientLogger("photoswipe");
7
7
 
8
8
  const GALLERY_SELECTOR = "a.pswp-gallery-item";
9
+ // The article header, summary and keyword span also carry data-pagefind-body.
10
+ // Selecting the first of those gave PhotoSwipe an empty gallery (counter "1 / 0").
11
+ const GALLERY_CONTAINER_SELECTOR = "[data-stalux-gallery='true'] [data-stalux-gallery-body]";
9
12
 
10
13
  async function enrichPhotoSwipeData(container: Element, signal: AbortSignal): Promise<void> {
11
14
  const links = container.querySelectorAll<HTMLAnchorElement>(GALLERY_SELECTOR);
@@ -40,9 +43,7 @@ async function enrichPhotoSwipeData(container: Element, signal: AbortSignal): Pr
40
43
  }
41
44
 
42
45
  registerPageLifecycle("photoswipe", () => {
43
- const container = document.querySelector<HTMLElement>(
44
- "[data-stalux-gallery='true'] [data-pagefind-body]",
45
- );
46
+ const container = document.querySelector<HTMLElement>(GALLERY_CONTAINER_SELECTOR);
46
47
  if (!container) return;
47
48
 
48
49
  const controller = new AbortController();
@@ -106,21 +106,47 @@ export function mountRandomPosts(): (() => void) | undefined {
106
106
  }
107
107
  }
108
108
  };
109
+ let started = false;
110
+ let observer: IntersectionObserver | undefined;
111
+ const start = () => {
112
+ if (started || disposed) return;
113
+ started = true;
114
+ observer?.disconnect();
115
+ observer = undefined;
116
+ void refresh();
117
+ };
109
118
  container.querySelector("#refresh-random-posts")?.addEventListener(
110
119
  "click",
111
120
  (event) => {
112
121
  event.preventDefault();
122
+ if (!started) {
123
+ start();
124
+ return;
125
+ }
113
126
  void refresh();
114
127
  },
115
128
  { signal: controller.signal },
116
129
  );
117
130
  reducedMotion.addEventListener("change", cancelAnimation, { signal: controller.signal });
118
- void refresh();
131
+ if ("IntersectionObserver" in window) {
132
+ // The sidebar sits below the article on phones; do not fetch its index
133
+ // until the reader is close enough to see it.
134
+ observer = new IntersectionObserver(
135
+ (entries) => {
136
+ if (entries.some((entry) => entry.isIntersecting)) start();
137
+ },
138
+ { rootMargin: "300px 0px" },
139
+ );
140
+ observer.observe(container);
141
+ } else {
142
+ start();
143
+ }
119
144
  return () => {
120
145
  disposed = true;
121
146
  revision++;
122
147
  request?.abort();
123
148
  controller.abort();
149
+ observer?.disconnect();
124
150
  cancelAnimation();
125
151
  container.removeAttribute("aria-busy");
126
152
  };
@@ -11,6 +11,7 @@ export interface CustomElementRegistryLike<ElementType> {
11
11
  export async function ensureSearchStyles(link: HTMLLinkElement | null): Promise<void> {
12
12
  if (!link) return;
13
13
  link.rel = "stylesheet";
14
+ if (!link.href && link.dataset?.href) link.href = link.dataset.href;
14
15
  link.media = "all";
15
16
  if (link.sheet) return;
16
17
  await new Promise<void>((resolve, reject) => {
@@ -126,6 +126,8 @@ export function installSwupRouter(): Swup {
126
126
  scroll,
127
127
  new SwupA11yPlugin({ headingSelector: "[data-stalux-motion-focus]" }),
128
128
  new SwupPreloadPlugin({
129
+ // The initial document is already present; avoid fetching it twice.
130
+ preloadInitialPage: false,
129
131
  preloadHoveredLinks: true,
130
132
  preloadVisibleLinks: false,
131
133
  throttle: 2,
@@ -0,0 +1,3 @@
1
+ @layer theme, base, components, utilities;
2
+ @layer components{.stalux-author{margin-top:4em}.stalux-author-figure{justify-content:center;align-items:center;display:flex}.stalux-author-figure img{box-shadow:0 0 10px var(--stalux-accent-color);border-radius:9999px;transition:transform .18s ease-out}.stalux-author-figure figcaption{text-shadow:var(--stalux-text-shadow);color:var(--white-full);margin-left:1em;font-size:2.5em;font-weight:700}.stalux-author-figure-column figcaption{text-align:center;margin-top:.5em;margin-left:0}@media (hover:hover) and (pointer:fine){.stalux-author-figure>a:hover img{transform:rotate(3deg)}}@media (width<=768px){.stalux-author{box-sizing:border-box;width:100%;max-width:95vw;margin-top:2em;padding:1em .5em}.stalux-author-figure{gap:.5em;flex-direction:column!important}.stalux-author-figure img{max-width:20vw;max-height:20vw;width:64px!important;height:64px!important}.stalux-author-figure figcaption{text-align:center;word-break:break-all;overflow-wrap:break-word;white-space:normal;max-width:90vw;margin-top:.5em;margin-left:0;font-size:1.5em}}}@layer utilities,theme,base;
3
+ /*$vite$:1*/
@@ -0,0 +1,3 @@
1
+ @layer theme, base, components, utilities;
2
+ @layer components{.stalux-card-post-categories,.stalux-card-post-tags{gap:.4rem}.stalux-card-post-category{background:var(--category-bg);color:#fff;border-radius:6px;padding:.18rem .5rem;font-size:.84rem;line-height:1;display:inline-block}.stalux-card-post-description{overflow-wrap:anywhere;color:var(--white-70p);line-height:1.6}.stalux-card-post-meta{gap:.6rem}.stalux-card-post-tag{background:var(--tag-bg);color:#fff;border-radius:9999px;padding:.22rem .7rem;font-size:.85rem;line-height:1;display:inline-block}.stalux-card-post-title{overflow-wrap:anywhere;margin:0;line-height:1.35}.stalux-card-read-more{align-self:flex-start;margin-top:.6rem}.stalux-post-card{box-sizing:border-box;cursor:pointer;gap:var(--space-lg);border-radius:var(--radius-lg);background-color:var(--card-bg);height:100%;padding:var(--space-lg);box-shadow:var(--card-box-shadow);transition:transform .22s cubic-bezier(.22,.72,.22,1)}.stalux-card-post-cover{border-radius:var(--radius-md);flex-shrink:0;height:180px;margin-bottom:.5rem;overflow:hidden}.stalux-post-list-container{width:100%;height:auto;margin:5px 0}.stalux-post-list-container ul{flex-wrap:wrap;gap:2rem;margin:3em;padding:0;list-style-type:none;display:flex}.stalux-post-list-container ul>li{flex:min(22rem,100%);min-width:0;margin:0}.stalux-card-post-title a{color:inherit;text-decoration:none}.stalux-post-card>a:first-child{width:100%;min-width:0;display:block}.stalux-card-post-cover img{object-fit:cover;width:100%;max-width:100%;height:100%;transition:transform var(--transition-fast)}.stalux-card-post-date,.stalux-card-post-updated{color:#e8f2ff;border-radius:10px;align-items:center;gap:.35rem;width:fit-content;padding:.28rem .6rem;font-size:.9rem;line-height:1.1;display:inline-flex}.stalux-card-post-date{background:var(--date-bg);border:1px solid var(--date-border);color:#e8f2ff}.stalux-card-post-updated{background:var(--updated-bg);border:1px solid var(--updated-border);color:#fff7de}.stalux-card-post-tags a,.stalux-card-post-categories a{text-decoration:none}.stalux-card-post-tags a:focus-visible .stalux-card-post-tag{background:var(--tag-bg-hover)}.stalux-card-post-categories a:focus-visible .stalux-card-post-category{background:var(--category-bg-hover)}@media (hover:hover) and (pointer:fine){.stalux-card-post-tags a:hover .stalux-card-post-tag{background:var(--tag-bg-hover)}.stalux-card-post-categories a:hover .stalux-card-post-category{background:var(--category-bg-hover)}.stalux-post-card-shell:hover .stalux-post-card .stalux-card-post-cover img{transform:scale(1.025)}.stalux-post-card-shell:hover .stalux-post-card{box-shadow:var(--card-box-shadow-hover)}}@media (width<=768px){.stalux-post-list-container ul{margin:1rem}}}@layer utilities,theme,base;
3
+ /*$vite$:1*/
@@ -0,0 +1,3 @@
1
+ @layer theme, base, components, utilities;
2
+ @layer components{.stalux-home-body{min-height:calc(100vh - var(--stalux-site-header-height));padding-bottom:var(--space-3xl);position:relative}.stalux-home-title{color:var(--white-full);text-shadow:var(--stalux-text-shadow);margin-top:1.5em;font-size:4em}.stalux-typewriter{min-height:2em;text-shadow:var(--stalux-text-shadow);margin-top:1.5em;font-size:2.5em}#stalux-home-show-post-list{cursor:pointer;text-shadow:var(--stalux-text-shadow);margin-top:3em;font-size:1.5em}.agent-home-summary{clip-path:inset(50%);white-space:normal;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}@media (width<=768px){.stalux-home-body{box-sizing:border-box;width:100%;max-width:95vw;height:auto;padding:2rem .5rem 1.5rem}.stalux-home-title{word-break:break-all;margin-top:1em;font-size:2em}#stalux-home-show-post-list{margin-top:2em;font-size:1.1em}}}@layer utilities,theme,base;
3
+ /*$vite$:1*/
@@ -1,3 +1,3 @@
1
1
  @layer theme, base, components, utilities;
2
- @layer components{.stalux-link-card{color:#fff;box-sizing:border-box;background:var(--surface-glass);width:100%;min-width:0;min-height:200px;box-shadow:var(--card-box-shadow);border-radius:10px;flex-direction:column;padding:16px;text-decoration:none;transition:transform .22s,background-color .22s,border-color .22s;display:flex;overflow:visible}.stalux-link-card-content{text-align:center;flex-direction:column;flex:1;display:flex}.stalux-links{box-sizing:border-box;grid-template-columns:repeat(auto-fill,minmax(min(180px,100%),1fr));gap:20px;width:100%;max-width:1600px;margin:30px auto;padding:0 1rem;list-style-type:none;display:grid}.stalux-link-card-avatar{justify-content:center;margin-bottom:12px;display:flex}.stalux-link-list-item{width:100%;min-width:0;display:flex}.stalux-link-shell{width:100%;min-width:0}.stalux-link-card-content{transition:transform .2s}.stalux-link-card-avatar img{object-fit:cover;border:2px solid var(--accent-60p);transform-origin:50%;border-radius:9999px;width:80px;height:80px;transition:transform .22s,border-color .22s}.stalux-link-card-title{white-space:nowrap;background:linear-gradient(90deg, var(--white-95p), var(--accent-90p));color:#0000;text-overflow:ellipsis;background-clip:text;margin:0 0 8px;font-size:1.1rem;overflow:hidden}.stalux-link-card-description{color:var(--white-78p);-webkit-line-clamp:3;text-overflow:ellipsis;-webkit-box-orient:vertical;margin:0;font-size:.95rem;line-height:1.45;display:-webkit-box;overflow:hidden}@media (1100px<=width<=1399px){.stalux-links{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (480px<=width<=767px){.stalux-links{grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}}@media (768px<=width<=1099px){.stalux-links{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (hover:hover) and (pointer:fine){.stalux-link-shell:hover .stalux-link-card-content{transform:translateY(-2px)}.stalux-link-shell:hover .stalux-link-card{background:var(--white-05p);box-shadow:var(--card-box-shadow-hover)}.stalux-link-card-avatar:hover img{border-color:var(--accent-85p);transform:rotate(3deg)}}@media (width>=1400px){.stalux-links{grid-template-columns:repeat(5,minmax(0,1fr))}}@media (width<=480px){.stalux-link-card{padding:14px}.stalux-link-card-avatar img{width:70px;height:70px}.stalux-link-card-title{font-size:1rem}.stalux-link-card-description{font-size:.9rem}}@media (width<=479px){.stalux-links{grid-template-columns:1fr;gap:14px;margin:20px auto}}@media (prefers-reduced-motion:reduce) and (hover:hover) and (pointer:fine){.stalux-link-card-avatar:hover img{transition:none;animation:none;transform:none}}}@layer utilities,theme,base;
2
+ @layer components{.stalux-link-card{color:#fff;box-sizing:border-box;background:var(--surface-glass);width:100%;min-width:0;min-height:200px;box-shadow:var(--card-box-shadow);border-radius:10px;flex-direction:column;padding:16px;text-decoration:none;transition:transform .22s,background-color .22s,border-color .22s;display:flex;overflow:visible}.stalux-link-card-content{text-align:center;flex-direction:column;flex:1;display:flex}.stalux-links{box-sizing:border-box;grid-template-columns:repeat(auto-fill,minmax(min(180px,100%),1fr));gap:20px;width:100%;max-width:1600px;margin:30px auto;padding:0 1rem;list-style-type:none;display:grid}.stalux-link-card-avatar{justify-content:center;margin-bottom:12px;display:flex}.stalux-link-list-item{width:100%;min-width:0;display:flex}.stalux-link-shell{width:100%;min-width:0}.stalux-link-card-content{transition:transform .2s}.stalux-link-card-avatar img{object-fit:cover;border:2px solid var(--accent-60p);transform-origin:50%;border-radius:9999px;width:80px;height:80px;transition:transform .22s,border-color .22s}.stalux-link-card-avatar img[data-src],.stalux-link-card-avatar img[data-stalux-link-icon-failed]{background:var(--white-05p)}.stalux-link-card-title{white-space:nowrap;background:linear-gradient(90deg, var(--white-95p), var(--accent-90p));color:#0000;text-overflow:ellipsis;background-clip:text;margin:0 0 8px;font-size:1.1rem;overflow:hidden}.stalux-link-card-description{color:var(--white-78p);-webkit-line-clamp:3;text-overflow:ellipsis;-webkit-box-orient:vertical;margin:0;font-size:.95rem;line-height:1.45;display:-webkit-box;overflow:hidden}@media (1100px<=width<=1399px){.stalux-links{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (480px<=width<=767px){.stalux-links{grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}}@media (768px<=width<=1099px){.stalux-links{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (hover:hover) and (pointer:fine){.stalux-link-shell:hover .stalux-link-card-content{transform:translateY(-2px)}.stalux-link-shell:hover .stalux-link-card{background:var(--white-05p);box-shadow:var(--card-box-shadow-hover)}.stalux-link-card-avatar:hover img{border-color:var(--accent-85p);transform:rotate(3deg)}}@media (width>=1400px){.stalux-links{grid-template-columns:repeat(5,minmax(0,1fr))}}@media (width<=480px){.stalux-link-card{padding:14px}.stalux-link-card-avatar img{width:70px;height:70px}.stalux-link-card-title{font-size:1rem}.stalux-link-card-description{font-size:.9rem}}@media (width<=479px){.stalux-links{grid-template-columns:1fr;gap:14px;margin:20px auto}}@media (prefers-reduced-motion:reduce) and (hover:hover) and (pointer:fine){.stalux-link-card-avatar:hover img{transition:none;animation:none;transform:none}}}@layer utilities,theme,base;
3
3
  /*$vite$:1*/
@@ -0,0 +1,3 @@
1
+ @layer theme, base, components, utilities;
2
+ @layer components{.stalux-404-home-btn{background:var(--black-20p);border:var(--border-thin) solid var(--white-20p);transition:background-color var(--transition-normal), transform var(--transition-normal);border-radius:var(--radius-md);padding:var(--space-md) var(--space-lg);color:var(--white-90p);font-size:var(--font-size-base);text-decoration:none;display:inline-block}.stalux-404-error-container{min-height:60vh;padding:2rem}.stalux-404-error-content h1{color:var(--white-90p);margin:0;font-size:6rem;font-weight:700}.stalux-404-error-content p{color:var(--white-70p);margin:1rem 0 2rem;font-size:1.5rem}.stalux-404-countdown-text{color:var(--white-50p)!important;margin:-.5rem 0 1.5rem!important;font-size:.95rem!important}.agent-recovery{max-width:42rem;line-height:1.7;margin:0 auto 1.5rem!important;font-size:1rem!important}.agent-recovery a{color:inherit;text-decoration-line:underline}@media (hover:hover) and (pointer:fine){.stalux-404-home-btn:hover{background:var(--accent-20p);box-shadow:0 4px 12px var(--shadow-dark)}}@media (width<=768px){.stalux-404-error-content h1{font-size:4rem}.stalux-404-error-content p{font-size:1.2rem}}}@layer utilities,theme,base;
3
+ /*$vite$:1*/
@@ -0,0 +1,3 @@
1
+ @layer theme, base, components, utilities;
2
+ @layer components{.stalux-social{margin-top:3em}.stalux-social ul{flex-wrap:wrap;justify-content:center;gap:1em;margin:0;padding:0;display:flex}.stalux-social ul li{box-sizing:border-box;text-align:center;background-color:var(--white-15p);border-radius:9999px;justify-content:center;align-items:center;width:2.5em;height:2.5em;font-size:large;display:inline-flex;overflow:hidden}.stalux-social ul a,.stalux-social ul .stalux-social-icon-container,.stalux-social ul .stalux-social-custom-icon{justify-content:center;align-items:center;width:100%;height:100%;line-height:1;display:inline-flex}.stalux-social[data-stalux-social-animate=true] li{animation:.28s cubic-bezier(.2,.8,.2,1) both stalux-social-stalux-social-arrive}.stalux-social[data-stalux-social-animate=true] li:nth-child(2){animation-delay:30ms}.stalux-social[data-stalux-social-animate=true] li:nth-child(3){animation-delay:60ms}.stalux-social[data-stalux-social-animate=true] li:nth-child(4){animation-delay:90ms}.stalux-social[data-stalux-social-animate=true] li:nth-child(5){animation-delay:.12s}.stalux-social[data-stalux-social-animate=true] li:nth-child(6){animation-delay:.15s}.stalux-social[data-stalux-social-animate=true] li:nth-child(n+7){animation-delay:.18s}@media (prefers-reduced-motion:reduce){.stalux-social[data-stalux-social-animate=true] li{animation:none}}}@layer utilities,theme,base;
3
+ /*$vite$:1*/
@@ -1,3 +1,3 @@
1
1
  @layer theme, base, components, utilities;
2
- @layer components{.stalux-404-home-btn{background:var(--black-20p);border:var(--border-thin) solid var(--white-20p);transition:background-color var(--transition-normal), transform var(--transition-normal);border-radius:var(--radius-md);padding:var(--space-md) var(--space-lg);color:var(--white-90p);font-size:var(--font-size-base);text-decoration:none;display:inline-block}.stalux-ai-prompt{clip-path:inset(50%);white-space:pre-wrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.stalux-badge-group{border:none}.stalux-badge-group-arrow{font-size:.65rem;transition:transform .2s;display:inline-block}.stalux-badge-group-summary{user-select:none;cursor:pointer;color:var(--white-70p);gap:.4rem;padding:.2rem 0;font-size:.85rem;list-style-type:none;transition:color .2s}.stalux-beian-public-security-beian{color:#ffffffb3;gap:5px;font-size:.9rem;text-decoration:none;transition:color .3s;display:inline-flex}.stalux-card-post-categories,.stalux-card-post-tags{gap:.4rem}.stalux-card-post-category{background:var(--category-bg);color:#fff;border-radius:6px;padding:.18rem .5rem;font-size:.84rem;line-height:1;display:inline-block}.stalux-card-post-description{overflow-wrap:anywhere;color:var(--white-70p);line-height:1.6}.stalux-card-post-meta{gap:.6rem}.stalux-card-post-tag{background:var(--tag-bg);color:#fff;border-radius:9999px;padding:.22rem .7rem;font-size:.85rem;line-height:1;display:inline-block}.stalux-card-post-title{overflow-wrap:anywhere;margin:0;line-height:1.35}.stalux-card-read-more{align-self:flex-start;margin-top:.6rem}.stalux-card-time{gap:.45rem;display:inline-flex}.stalux-copyright{color:var(--white-70p);gap:.3em;font-weight:500}.stalux-footer{backdrop-filter:blur(10px);color:var(--white-full);background-color:#0000001a;margin-top:auto;padding:1.5rem 0;font-size:.9rem}.stalux-footer-content{gap:.8rem;max-width:1200px;margin:0 auto;padding:0 1rem}.stalux-nav-list{transform:scale(var(--nav-scale,1));transform-origin:50%;box-sizing:border-box;gap:2rem;max-width:100%;transition:transform .22s cubic-bezier(.22,.72,.22,1);overflow:visible}.stalux-nav-overlay{pointer-events:none;z-index:999;opacity:0;background:#0009;height:100%;transition:opacity .3s;display:none;position:fixed;top:0;left:0}.stalux-nav-scroll-btn{background:var(--black-30p);backdrop-filter:blur(8px);box-shadow:var(--shadow-md);pointer-events:none;z-index:10;cursor:pointer;width:36px;height:36px;color:var(--white-full);opacity:0;border:none;border-radius:9999px;padding:0;transition:opacity .25s;display:none;position:absolute;top:50%;transform:translateY(-50%)}.stalux-nav-toggle{z-index:998;cursor:pointer;min-width:48px;min-height:48px;color:inherit;background:0 0;border:none;padding:.75rem;display:none}.stalux-post-card{box-sizing:border-box;cursor:pointer;gap:var(--space-lg);border-radius:var(--radius-lg);background-color:var(--card-bg);height:100%;padding:var(--space-lg);box-shadow:var(--card-box-shadow);transition:transform .22s cubic-bezier(.22,.72,.22,1)}.stalux-stats-site-build-time{color:#fffc;margin-top:.5rem;font-size:.9rem}.stalux-stats-stats-separator{color:#ffffff80}.stalux-theme-info{color:var(--white-70p);gap:.3em;font-size:.85rem}.stalux-home-body{min-height:calc(100vh - var(--stalux-site-header-height));padding-bottom:var(--space-3xl);position:relative}.stalux-nav-container{box-sizing:border-box;max-width:100%;position:relative;overflow:hidden}.stalux-author{margin-top:4em}.stalux-card-post-cover{border-radius:var(--radius-md);flex-shrink:0;height:180px;margin-bottom:.5rem;overflow:hidden}.stalux-home-title{color:var(--white-full);text-shadow:var(--stalux-text-shadow);margin-top:1.5em;font-size:4em}.stalux-nav-item{text-shadow:var(--stalux-text-shadow);flex-shrink:0;margin-left:1em;margin-right:1em;font-size:1.3em}.stalux-page-description{color:var(--white-60p);margin-bottom:1.5em;font-size:1.2em}.stalux-page-title{text-shadow:var(--stalux-text-shadow);margin-top:1.5em;font-size:2.5em}.stalux-typewriter{min-height:2em;text-shadow:var(--stalux-text-shadow);margin-top:1.5em;font-size:2.5em}.stalux-404-error-container{min-height:60vh;padding:2rem}.stalux-badge,.stalux-badges-wrapper .stalux-badge{width:auto}.stalux-u-min-w-0{min-width:0}.stalux-u-w-full{width:100%}.stalux-u-flex{display:flex}.stalux-u-flex-col{flex-direction:column}.stalux-u-flex-wrap{flex-wrap:wrap}.stalux-u-items-center{align-items:center}.stalux-u-justify-center{justify-content:center}.stalux-badges,.stalux-badges-wrapper{gap:.5rem}.stalux-beian-beian-info{gap:1rem}.stalux-stats-site-stats{gap:.5rem;font-size:.9rem}.stalux-nav-text{white-space:nowrap;color:var(--white-full)}.stalux-badge-group-items{padding-top:.35rem}.stalux-u-text-center{text-align:center}.stalux-badge-group-hint{color:var(--white-70p);font-size:.7rem}.stalux-badge-group-title{color:var(--white-80p)}#stalux-home-show-post-list{cursor:pointer;text-shadow:var(--stalux-text-shadow);margin-top:3em;font-size:1.5em}.stalux-post-list-container{width:100%;height:auto;margin:5px 0}.stalux-post-list-container ul{flex-wrap:wrap;gap:2rem;margin:3em;padding:0;list-style-type:none;display:flex}.stalux-post-list-container ul>li{flex:min(22rem,100%);min-width:0;margin:0}.stalux-card-post-title a{color:inherit;text-decoration:none}.stalux-post-card>a:first-child{width:100%;min-width:0;display:block}.stalux-card-post-cover img{object-fit:cover;width:100%;max-width:100%;height:100%;transition:transform var(--transition-fast)}.stalux-card-post-date,.stalux-card-post-updated{color:#e8f2ff;border-radius:10px;align-items:center;gap:.35rem;width:fit-content;padding:.28rem .6rem;font-size:.9rem;line-height:1.1;display:inline-flex}.stalux-card-post-date{background:var(--date-bg);border:1px solid var(--date-border);color:#e8f2ff}.stalux-card-post-updated{background:var(--updated-bg);border:1px solid var(--updated-border);color:#fff7de}.stalux-card-post-tags a,.stalux-card-post-categories a{text-decoration:none}.stalux-card-post-tags a:focus-visible .stalux-card-post-tag{background:var(--tag-bg-hover)}.stalux-card-post-categories a:focus-visible .stalux-card-post-category{background:var(--category-bg-hover)}.stalux-social{margin-top:3em}.stalux-social ul{flex-wrap:wrap;justify-content:center;gap:1em;margin:0;padding:0;display:flex}.stalux-social ul li{box-sizing:border-box;text-align:center;background-color:var(--white-15p);border-radius:9999px;justify-content:center;align-items:center;width:2.5em;height:2.5em;font-size:large;display:inline-flex;overflow:hidden}.stalux-social ul a,.stalux-social ul .stalux-social-icon-container,.stalux-social ul .stalux-social-custom-icon{justify-content:center;align-items:center;width:100%;height:100%;line-height:1;display:inline-flex}.stalux-social[data-stalux-social-animate=true] li{animation:.28s cubic-bezier(.2,.8,.2,1) both stalux-social-stalux-social-arrive}.stalux-social[data-stalux-social-animate=true] li:nth-child(2){animation-delay:30ms}.stalux-social[data-stalux-social-animate=true] li:nth-child(3){animation-delay:60ms}.stalux-social[data-stalux-social-animate=true] li:nth-child(4){animation-delay:90ms}.stalux-social[data-stalux-social-animate=true] li:nth-child(5){animation-delay:.12s}.stalux-social[data-stalux-social-animate=true] li:nth-child(6){animation-delay:.15s}.stalux-social[data-stalux-social-animate=true] li:nth-child(n+7){animation-delay:.18s}nav{width:100%;min-width:0;max-width:100%}html{scroll-padding-top:var(--stalux-nav-offset,var(--stalux-site-header-height))}.stalux-site-header{z-index:990;width:100%;min-height:var(--stalux-site-header-min-height);isolation:isolate;justify-content:center;align-items:center;display:flex;position:sticky;top:0}.stalux-nav-scroll-btn[data-visible]{pointer-events:auto;opacity:1}.stalux-nav-scroll-btn[data-side=left]{left:4px}.stalux-nav-scroll-btn[data-side=right]{right:4px}.stalux-nav-scroll-btn svg{stroke:currentColor;stroke-width:2px;fill:none;width:20px;height:20px}.stalux-nav-link,.stalux-nav-link:visited{cursor:pointer;width:100%;color:inherit;min-height:44px;font:inherit;background:0 0;border:none;align-items:center;gap:.5rem;padding:.5rem .75rem;text-decoration:none;display:flex}.stalux-nav-link .nav-icon{stroke:var(--white-full)}.stalux-nav-toggle svg{width:32px;height:32px}.stalux-nav-overlay[data-state=show]{pointer-events:auto;opacity:1;display:block}#stalux-to-archive-archs{margin-top:2em}#stalux-date-date-time{text-shadow:var(--stalux-text-shadow);color:var(--white-full);flex-direction:column;align-items:center;margin-top:2em;display:flex}#stalux-date-date-time #stalux-date-date{min-height:1.25em;margin-bottom:.5em;font-size:2em;line-height:1.25}#stalux-date-date-time #stalux-date-time{font-variant-numeric:tabular-nums;min-height:1.25em;font-size:3em;line-height:1.25}.stalux-author-figure{justify-content:center;align-items:center;display:flex}.stalux-author-figure img{box-shadow:0 0 10px var(--stalux-accent-color);border-radius:9999px;transition:transform .18s ease-out}.stalux-author-figure figcaption{text-shadow:var(--stalux-text-shadow);color:var(--white-full);margin-left:1em;font-size:2.5em;font-weight:700}.stalux-author-figure-column figcaption{text-align:center;margin-top:.5em;margin-left:0}.agent-home-summary{clip-path:inset(50%);white-space:normal;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.stalux-404-error-content h1{color:var(--white-90p);margin:0;font-size:6rem;font-weight:700}.stalux-404-error-content p{color:var(--white-70p);margin:1rem 0 2rem;font-size:1.5rem}.stalux-404-countdown-text{color:var(--white-50p)!important;margin:-.5rem 0 1.5rem!important;font-size:.95rem!important}.agent-recovery{max-width:42rem;line-height:1.7;margin:0 auto 1.5rem!important;font-size:1rem!important}.agent-recovery a{color:inherit;text-decoration-line:underline}.stalux-theme-info span,.stalux-copyright span{margin:0}.stalux-theme-info a{color:#fffc;margin:0 .2em;text-decoration:none;transition:color .3s}.stalux-beian-beian-info a{color:#ffffffb3;font-size:.9rem;text-decoration:none;transition:color .3s}.stalux-beian-public-security-beian img{width:15px;height:15px;display:inline-block}.stalux-badge-group-summary::-webkit-details-marker,.stalux-badge-group[open] .stalux-badge-group-hint[data-hint=collapsed],.stalux-badge-group:not([open]) .stalux-badge-group-hint[data-hint=expanded]{display:none}.stalux-badge-group[open] .stalux-badge-group-arrow{transform:rotate(90deg)}@media (hover:hover) and (pointer:fine){.stalux-card-post-tags a:hover .stalux-card-post-tag{background:var(--tag-bg-hover)}.stalux-card-post-categories a:hover .stalux-card-post-category{background:var(--category-bg-hover)}.stalux-post-card-shell:hover .stalux-post-card .stalux-card-post-cover img{transform:scale(1.025)}.stalux-post-card-shell:hover .stalux-post-card{box-shadow:var(--card-box-shadow-hover)}.stalux-nav-scroll-btn:hover{background:0 0}.stalux-nav-container:hover .stalux-nav-scroll-btn[data-visible]{opacity:1}.stalux-nav-link:hover .nav-icon,.stalux-nav-link:hover .stalux-nav-text{stroke:var(--stalux-accent-color);color:var(--stalux-accent-color)}.stalux-author-figure>a:hover img{transform:rotate(3deg)}.stalux-404-home-btn:hover{background:var(--accent-20p);box-shadow:0 4px 12px var(--shadow-dark)}.stalux-theme-info a:hover{color:#fff;text-decoration:none}.stalux-beian-beian-info a:hover,.stalux-beian-public-security-beian:hover{color:#ffffffe6;text-decoration:none}.stalux-badge-group-summary:hover{color:var(--white-70p)}}@media (width>=601px){.stalux-site-nav{isolation:isolate;border-radius:0 0 16px 16px;transition:background-color .18s}.stalux-site-nav[data-scrolled]{backdrop-filter:blur(20px)saturate(140%);box-shadow:0 1px 0 var(--white-10p), 0 8px 24px #0000001f;background:#181818c7}}@media (width<=1100px){.stalux-nav-list{gap:1rem}.stalux-nav-item{margin-left:.6em;margin-right:.6em;font-size:1em}}@media (width<=900px){.stalux-nav-item{margin-left:.25em;margin-right:.25em;font-size:.95em}.stalux-nav-list{gap:.5rem}}@media (width<=768px){.stalux-home-body{box-sizing:border-box;width:100%;max-width:95vw;height:auto;padding:2rem .5rem 1.5rem}.stalux-home-title{word-break:break-all;margin-top:1em;font-size:2em}#stalux-home-show-post-list{margin-top:2em;font-size:1.1em}.stalux-post-list-container ul{margin:1rem}.stalux-footer{padding:1.2rem 0;font-size:.8rem}.stalux-footer-content{gap:.6rem;padding:0 .8rem}.stalux-author{box-sizing:border-box;width:100%;max-width:95vw;margin-top:2em;padding:1em .5em}.stalux-author-figure{gap:.5em;flex-direction:column!important}.stalux-author-figure img{max-width:20vw;max-height:20vw;width:64px!important;height:64px!important}.stalux-author-figure figcaption{text-align:center;word-break:break-all;overflow-wrap:break-word;white-space:normal;max-width:90vw;margin-top:.5em;margin-left:0;font-size:1.5em}.stalux-404-error-content h1{font-size:4rem}.stalux-404-error-content p{font-size:1.2rem}.stalux-badges{gap:.3rem}.stalux-badge{width:auto}}@media (width<=600px){html{--stalux-site-header-min-height:4rem}.stalux-nav-toggle[data-state=open]{visibility:hidden;pointer-events:none}.stalux-nav-toggle[data-scrolled]{backdrop-filter:blur(20px)saturate(140%);box-shadow:0 1px 0 var(--white-10p), 0 8px 24px #0000001f;background:#181818c7;border-radius:12px}.stalux-nav-toggle{display:block;position:fixed;top:.75rem;left:.75rem}.stalux-nav-container{overflow:visible}nav{width:auto}.stalux-nav-list{pointer-events:none;z-index:1000;background:var(--black-20p);backdrop-filter:blur(20px);flex-direction:column;justify-content:flex-start;align-items:flex-start;gap:0;width:min(220px,100vw - 1rem);height:100vh;padding:.5rem 1rem 2rem;position:fixed;top:0;left:0;right:auto;overflow-y:auto;transform:translate(-100%)}.stalux-nav-list[data-state=open]{pointer-events:auto;transform:translate(0)}.stalux-nav-item{border-bottom:1px solid var(--white-10p);flex-shrink:0;width:100%;margin:0;font-size:.9em}.stalux-nav-text{display:inline}.stalux-nav-link{width:100%;min-height:44px;font-size:inherit;padding:.45rem .75rem}.stalux-nav-link .nav-icon{width:18px;height:18px}}@media (width<=480px){.stalux-footer{padding:1rem 0;font-size:.8rem}.stalux-footer-content{gap:.6rem;padding:0 .8rem}}@media (prefers-reduced-motion:reduce){.stalux-social[data-stalux-social-animate=true] li{animation:none}}.stalux-main,.stalux-post-page,.stalux-post-layout>*,.stalux-link-card-content,.stalux-word-list-item,.stalux-word-card{box-sizing:border-box;min-width:0;max-width:100%}.stalux-main{overflow-wrap:anywhere}.stalux-link-shell{min-width:0;max-width:100%;display:block}.stalux-link-card{box-sizing:border-box;width:100%;max-width:100%}.stalux-category-shell{width:100%;min-width:0}.stalux-author-figure>a{flex-shrink:0;display:inline-block}.stalux-author-figure img{display:block}.stalux-post-card-shell,.stalux-category-shell,.stalux-link-shell{position:relative}.stalux-post-card,.stalux-category-card,.stalux-link-card,.stalux-word-card{transition:transform .2s,background-color .2s,border-color .2s,box-shadow .2s}:is(.stalux-post-card-shell,.stalux-category-shell):focus-within>*,.stalux-link-shell:focus-visible>*{box-shadow:var(--card-box-shadow-hover)}:is(button,summary,[role=button]):focus-visible{outline:2px solid var(--stalux-accent-color);outline-offset:3px}@media (prefers-reduced-motion:reduce){.stalux-main *,.stalux-main :before,.stalux-main :after{transition-duration:0s!important;animation:none!important}.stalux-post-card,.stalux-category-card,.stalux-link-card,.stalux-link-card-content,.stalux-card-post-cover img,.stalux-category-icon,.stalux-author-figure img,.stalux-link-card-avatar img{transform:none!important}}@keyframes stalux-social-stalux-social-arrive{0%{opacity:0;transform:translateY(.75rem)rotate(-12deg)}to{opacity:1;transform:translateY(0)rotate(0)}}@keyframes stalux-random-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes stalux-link-avatar-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}}@layer utilities{.sr-only{clip:rect(0,0,0,0);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.visible{visibility:visible}.absolute{position:absolute}.static{position:static}.grid{display:grid}.block{display:block}.inline-block{display:inline-block}.hidden{display:none}.h-full{height:100%}.flex{display:flex}.flex-shrink{flex-shrink:1}.flex-wrap{flex-wrap:wrap}.transform{transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z))}.b,.border{border-width:1px}.text-inherit{color:inherit}.no-underline{text-decoration:none}.outline{outline-style:solid}}@layer theme;@layer base{html,body{color:#fff;width:100%;height:100%;margin:0;& main{flex:1}scrollbar-color:#4a4a4a #2a2a2a;font-family:var(--font-body), "Noto Sans SC", "Noto Sans CJK SC", system-ui, -apple-system, sans-serif;text-rendering:optimizelegibility;line-height:1.25;display:block}body{height:auto;min-height:100%}html{scrollbar-gutter:stable;background-color:var(--stalux-bg-color,#202020)}html[data-clarity-id]{opacity:1!important}a{color:inherit;cursor:pointer;text-decoration-skip-ink:auto;background-color:#0000;text-decoration:none;transition:color .2s,text-shadow .2s;position:relative}.a-none{cursor:pointer;text-decoration-skip-ink:auto;background-color:#0000;color:inherit!important;text-decoration:none!important}.a-none:after{display:none!important}a:not(.a-none):not([data-stalux-card]):not(.stalux-nav-link):after{content:"";background:linear-gradient(90deg, var(--stalux-accent-color), var(--accent-50p));transform-origin:0;pointer-events:none;width:100%;height:1px;transition:transform .25s cubic-bezier(.25,.9,.2,1);position:absolute;bottom:-2px;left:0;transform:scaleX(0)}a:not(.a-none):not([data-stalux-card]):not(.stalux-nav-link):focus-visible:after,a:not(.a-none):not([data-stalux-card]):not(.stalux-nav-link):active:after{transform:scaleX(1)}@media (hover:hover) and (pointer:fine){a:not(.a-none):not([data-stalux-card]):not(.stalux-nav-link):hover:after{transform:scaleX(1)}}a:active,a:focus-visible{text-decoration:none}@media (hover:hover) and (pointer:fine){a:hover{text-decoration:none}}a:focus{outline:none}a:focus-visible{outline:2px solid var(--stalux-accent-color);outline-offset:2px}ul{margin:0;padding:0;list-style:none}.none{display:none}.stalux-root{color:#fff;min-height:100vh}.stalux-main{min-height:calc(100vh - var(--stalux-site-header-height,3rem))}:root{--stalux-bg-color:#202020;--stalux-bg-filter:none;--stalux-site-header-min-height:3.75rem;--stalux-site-header-height:var(--stalux-site-header-min-height);--stalux-text-shadow:var(--stalux-accent-color) .08rem .08rem .18rem;--stalux-accent-color:#eab308;--stalux-accent-rgb:234 179 8;--surface-glass:var(--black-05p);--surface-glass-shadow:var(--shadow-sidebar);--card-bg:var(--surface-glass);--card-border:#ffffff1a;--card-box-shadow:0 10px 24px #00000014;--card-box-shadow-hover:0 14px 28px #0000001f;--tag-bg:#ffffff1f;--tag-bg-hover:#ffffff2e;--category-bg:#ffffff14;--category-bg-hover:#ffffff24;--date-bg:#42a0ff2e;--date-border:#42a0ff40;--updated-bg:#ffc94733;--updated-border:#ffc9474d;--white-full:#fff;--white-95p:#fffffff2;--white-90p:#ffffffe6;--white-85p:#ffffffd9;--white-80p:#ffffffe6;--white-78p:#ffffffc7;--white-75p:#ffffffbf;--white-70p:#ffffffdb;--white-60p:#fff9;--white-50p:#ffffff80;--white-30p:#ffffff4d;--white-20p:#fff3;--white-15p:#ffffff26;--white-10p:#ffffff1a;--white-05p:#ffffff0d;--black-30p:#0000004d;--black-20p:#0003;--black-15p:#00000026;--black-10p:#0000001a;--black-05p:#0000000d;--accent-90p:rgb(var(--stalux-accent-rgb) / .9);--accent-85p:rgb(var(--stalux-accent-rgb) / .85);--accent-80p:rgb(var(--stalux-accent-rgb) / .8);--accent-70p:rgb(var(--stalux-accent-rgb) / .7);--accent-60p:rgb(var(--stalux-accent-rgb) / .6);--accent-50p:rgb(var(--stalux-accent-rgb) / .5);--accent-40p:rgb(var(--stalux-accent-rgb) / .4);--accent-30p:rgb(var(--stalux-accent-rgb) / .3);--accent-20p:rgb(var(--stalux-accent-rgb) / .2);--transition-fast:.18s ease;--transition-medium:.2s ease;--transition-normal:.25s ease;--transition-slow:.3s ease;--transition-slower:.5s ease;--transition-slowest:.6s ease;--easing-bounce:cubic-bezier(.175, .885, .32, 1.275);--space-2xs:.25rem;--space-xs:.75rem;--space-sm:.5rem;--space-md:1rem;--space-lg:1.5rem;--space-xl:2rem;--space-2xl:3rem;--space-3xl:4rem;--radius-sm:4px;--radius-md:6px;--radius-lg:8px;--radius-full:9999px;--shadow-sm:0 2px 5px #0000001a;--shadow-md:0 6px 16px #0003;--shadow-lg:0 8px 20px #0000004d;--shadow-xl:0 12px 26px #00000047;--shadow-sidebar:0 16px 20px -12px #000000d6, 10px 4px 16px -12px #00000094, -10px 4px 16px -12px #00000094;--shadow-raised-hover:0 20px 24px -13px #000000e6, 12px 5px 18px -13px #000000ad, -12px 5px 18px -13px #000000ad;--shadow-dark:#000000c0;--muted-text:#bbb;--font-body:"Noto Sans SC", "Noto Sans CJK SC", system-ui, sans-serif;--font-size-subtitle:1.35rem;--font-size-base:1.1rem;--font-size-small:.95rem;--font-size-smaller:.9rem;--font-size-tiny:.85rem;--border-thin:1px;--border-medium:2px;--border-thick:3px}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:#2a2a2a;border-radius:4px}::-webkit-scrollbar-thumb{background:#4a4a4a;border-radius:4px;transition:background .3s ease-out}@media (hover:hover) and (pointer:fine){::-webkit-scrollbar-thumb:hover{background:#5a5a5a}}::-webkit-scrollbar-corner{background:#2a2a2a}.bg-layer{z-index:-10;background-position:50%;background-repeat:repeat;background-size:auto;background-color:var(--stalux-bg-color,transparent);filter:var(--stalux-bg-filter,none);pointer-events:none;opacity:0;forced-color-adjust:none;color-scheme:light only;transition:opacity .2s ease-out;position:fixed;inset:0}@media (prefers-reduced-motion:reduce){.bg-layer{transition-duration:0s}}footer{padding:var(--space-lg) var(--space-md);text-align:center;color:var(--muted-text);border-top:1px solid var(--white-10p);backdrop-filter:blur(16px);background:linear-gradient(#0c0e122e,#0c0e12b8)}}@media print{[data-stalux-offscreen-code],[data-stalux-offscreen-svg]{content-visibility:visible!important;contain-intrinsic-block-size:none!important}}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}[data-stalux-home-content]{flex-direction:column;align-items:center;width:100%;display:flex}@keyframes stalux-page-exit{0%{opacity:1}to{opacity:1}}@keyframes stalux-page-enter{0%{opacity:.9}to{opacity:1}}@keyframes stalux-page-enter-back{0%{opacity:.96}to{opacity:1}}@keyframes stalux-taxonomy-enter{0%{opacity:.9}to{opacity:1}}@keyframes stalux-taxonomy-enter-back{0%{opacity:.96}to{opacity:1}}@keyframes stalux-article-enter{0%{opacity:.9}to{opacity:1}}@keyframes stalux-article-enter-back{0%{opacity:.96}to{opacity:1}}@keyframes stalux-sidebar-left-in{0%{opacity:.96}to{opacity:1}}@keyframes stalux-sidebar-left-out{0%{opacity:1}to{opacity:1}}@keyframes stalux-sidebar-left-back-in{0%{opacity:.96}to{opacity:1}}@keyframes stalux-sidebar-left-back-out{0%{opacity:1}to{opacity:1}}@keyframes stalux-sidebar-right-in{0%{opacity:.96}to{opacity:1}}@keyframes stalux-sidebar-right-out{0%{opacity:1}to{opacity:1}}@keyframes stalux-sidebar-right-back-in{0%{opacity:.96}to{opacity:1}}@keyframes stalux-sidebar-right-back-out{0%{opacity:1}to{opacity:1}}[data-stalux-scroll-sentinel]{visibility:hidden;pointer-events:none;width:1px;height:1px;position:absolute;top:240px;left:0}@media (prefers-reduced-motion:reduce){.stalux-main,.stalux-main *,.postLayout *{scroll-behavior:auto!important;transition-duration:1ms!important;animation-duration:1ms!important;animation-iteration-count:1!important}}
2
+ @layer components{.stalux-ai-prompt{clip-path:inset(50%);white-space:pre-wrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.stalux-badge-group{border:none}.stalux-badge-group-arrow{font-size:.65rem;transition:transform .2s;display:inline-block}.stalux-badge-group-summary{user-select:none;cursor:pointer;color:var(--white-70p);gap:.4rem;padding:.2rem 0;font-size:.85rem;list-style-type:none;transition:color .2s}.stalux-beian-public-security-beian{color:#ffffffb3;gap:5px;font-size:.9rem;text-decoration:none;transition:color .3s;display:inline-flex}.stalux-card-time{gap:.45rem;display:inline-flex}.stalux-copyright{color:var(--white-70p);gap:.3em;font-weight:500}.stalux-footer{backdrop-filter:blur(10px);color:var(--white-full);background-color:#0000001a;margin-top:auto;padding:1.5rem 0;font-size:.9rem}.stalux-footer-content{gap:.8rem;max-width:1200px;margin:0 auto;padding:0 1rem}.stalux-nav-list{transform:scale(var(--nav-scale,1));transform-origin:50%;box-sizing:border-box;gap:2rem;max-width:100%;transition:transform .22s cubic-bezier(.22,.72,.22,1);overflow:visible}.stalux-nav-overlay{pointer-events:none;z-index:999;opacity:0;background:#0009;height:100%;transition:opacity .3s;display:none;position:fixed;top:0;left:0}.stalux-nav-scroll-btn{background:var(--black-30p);backdrop-filter:blur(8px);box-shadow:var(--shadow-md);pointer-events:none;z-index:10;cursor:pointer;width:36px;height:36px;color:var(--white-full);opacity:0;border:none;border-radius:9999px;padding:0;transition:opacity .25s;display:none;position:absolute;top:50%;transform:translateY(-50%)}.stalux-nav-toggle{z-index:998;cursor:pointer;min-width:48px;min-height:48px;color:inherit;background:0 0;border:none;padding:.75rem;display:none}.stalux-stats-site-build-time{color:#fffc;margin-top:.5rem;font-size:.9rem}.stalux-stats-stats-separator{color:#ffffff80}.stalux-theme-info{color:var(--white-70p);gap:.3em;font-size:.85rem}.stalux-nav-container{box-sizing:border-box;max-width:100%;position:relative;overflow:hidden}.stalux-nav-item{text-shadow:var(--stalux-text-shadow);flex-shrink:0;margin-left:1em;margin-right:1em;font-size:1.3em}.stalux-page-description{color:var(--white-60p);margin-bottom:1.5em;font-size:1.2em}.stalux-page-title{text-shadow:var(--stalux-text-shadow);margin-top:1.5em;font-size:2.5em}.stalux-badge,.stalux-badges-wrapper .stalux-badge{width:auto}.stalux-u-min-w-0{min-width:0}.stalux-u-w-full{width:100%}.stalux-u-flex{display:flex}.stalux-u-flex-col{flex-direction:column}.stalux-u-flex-wrap{flex-wrap:wrap}.stalux-u-items-center{align-items:center}.stalux-u-justify-center{justify-content:center}.stalux-badges,.stalux-badges-wrapper{gap:.5rem}.stalux-beian-beian-info{gap:1rem}.stalux-stats-site-stats{gap:.5rem;font-size:.9rem}.stalux-nav-text{white-space:nowrap;color:var(--white-full)}.stalux-badge-group-items{padding-top:.35rem}.stalux-u-text-center{text-align:center}.stalux-badge-group-hint{color:var(--white-70p);font-size:.7rem}.stalux-badge-group-title{color:var(--white-80p)}nav{width:100%;min-width:0;max-width:100%}html{scroll-padding-top:var(--stalux-nav-offset,var(--stalux-site-header-height))}.stalux-site-header{z-index:990;width:100%;min-height:var(--stalux-site-header-min-height);isolation:isolate;justify-content:center;align-items:center;display:flex;position:sticky;top:0}.stalux-nav-scroll-btn[data-visible]{pointer-events:auto;opacity:1}.stalux-nav-scroll-btn[data-side=left]{left:4px}.stalux-nav-scroll-btn[data-side=right]{right:4px}.stalux-nav-scroll-btn svg{stroke:currentColor;stroke-width:2px;fill:none;width:20px;height:20px}.stalux-nav-link,.stalux-nav-link:visited{cursor:pointer;width:100%;color:inherit;min-height:44px;font:inherit;background:0 0;border:none;align-items:center;gap:.5rem;padding:.5rem .75rem;text-decoration:none;display:flex}.stalux-nav-link .nav-icon{stroke:var(--white-full)}.stalux-nav-toggle svg{width:32px;height:32px}.stalux-nav-overlay[data-state=show]{pointer-events:auto;opacity:1;display:block}#stalux-to-archive-archs{margin-top:2em}#stalux-date-date-time{text-shadow:var(--stalux-text-shadow);color:var(--white-full);flex-direction:column;align-items:center;margin-top:2em;display:flex}#stalux-date-date-time #stalux-date-date{min-height:1.25em;margin-bottom:.5em;font-size:2em;line-height:1.25}#stalux-date-date-time #stalux-date-time{font-variant-numeric:tabular-nums;min-height:1.25em;font-size:3em;line-height:1.25}.stalux-theme-info span,.stalux-copyright span{margin:0}.stalux-theme-info a{color:#fffc;margin:0 .2em;text-decoration:none;transition:color .3s}.stalux-beian-beian-info a{color:#ffffffb3;font-size:.9rem;text-decoration:none;transition:color .3s}.stalux-beian-public-security-beian img{width:15px;height:15px;display:inline-block}.stalux-badge-group-summary::-webkit-details-marker,.stalux-badge-group[open] .stalux-badge-group-hint[data-hint=collapsed],.stalux-badge-group:not([open]) .stalux-badge-group-hint[data-hint=expanded]{display:none}.stalux-badge-group[open] .stalux-badge-group-arrow{transform:rotate(90deg)}@media (hover:hover) and (pointer:fine){.stalux-nav-scroll-btn:hover{background:0 0}.stalux-nav-container:hover .stalux-nav-scroll-btn[data-visible]{opacity:1}.stalux-nav-link:hover .nav-icon,.stalux-nav-link:hover .stalux-nav-text{stroke:var(--stalux-accent-color);color:var(--stalux-accent-color)}.stalux-theme-info a:hover{color:#fff;text-decoration:none}.stalux-beian-beian-info a:hover,.stalux-beian-public-security-beian:hover{color:#ffffffe6;text-decoration:none}.stalux-badge-group-summary:hover{color:var(--white-70p)}}@media (width>=601px){.stalux-site-nav{isolation:isolate;border-radius:0 0 16px 16px;transition:background-color .18s}.stalux-site-nav[data-scrolled]{backdrop-filter:blur(20px)saturate(140%);box-shadow:0 1px 0 var(--white-10p), 0 8px 24px #0000001f;background:#181818c7}}@media (width<=1100px){.stalux-nav-list{gap:1rem}.stalux-nav-item{margin-left:.6em;margin-right:.6em;font-size:1em}}@media (width<=900px){.stalux-nav-item{margin-left:.25em;margin-right:.25em;font-size:.95em}.stalux-nav-list{gap:.5rem}}@media (width<=768px){.stalux-footer{padding:1.2rem 0;font-size:.8rem}.stalux-footer-content{gap:.6rem;padding:0 .8rem}.stalux-badges{gap:.3rem}.stalux-badge{width:auto}}@media (width<=600px){html{--stalux-site-header-min-height:4rem}.stalux-nav-toggle[data-state=open]{visibility:hidden;pointer-events:none}.stalux-nav-toggle[data-scrolled]{backdrop-filter:blur(20px)saturate(140%);box-shadow:0 1px 0 var(--white-10p), 0 8px 24px #0000001f;background:#181818c7;border-radius:12px}.stalux-nav-toggle{display:block;position:fixed;top:.75rem;left:.75rem}.stalux-nav-container{overflow:visible}nav{width:auto}.stalux-nav-list{pointer-events:none;z-index:1000;background:var(--black-20p);backdrop-filter:blur(20px);flex-direction:column;justify-content:flex-start;align-items:flex-start;gap:0;width:min(220px,100vw - 1rem);height:100vh;padding:.5rem 1rem 2rem;position:fixed;top:0;left:0;right:auto;overflow-y:auto;transform:translate(-100%)}.stalux-nav-list[data-state=open]{pointer-events:auto;transform:translate(0)}.stalux-nav-item{border-bottom:1px solid var(--white-10p);flex-shrink:0;width:100%;margin:0;font-size:.9em}.stalux-nav-text{display:inline}.stalux-nav-link{width:100%;min-height:44px;font-size:inherit;padding:.45rem .75rem}.stalux-nav-link .nav-icon{width:18px;height:18px}}@media (width<=480px){.stalux-footer{padding:1rem 0;font-size:.8rem}.stalux-footer-content{gap:.6rem;padding:0 .8rem}}.stalux-main,.stalux-post-page,.stalux-post-layout>*,.stalux-link-card-content,.stalux-word-list-item,.stalux-word-card{box-sizing:border-box;min-width:0;max-width:100%}.stalux-main{overflow-wrap:anywhere}.stalux-link-shell{min-width:0;max-width:100%;display:block}.stalux-link-card{box-sizing:border-box;width:100%;max-width:100%}.stalux-category-shell{width:100%;min-width:0}.stalux-author-figure>a{flex-shrink:0;display:inline-block}.stalux-author-figure img{display:block}.stalux-post-card-shell,.stalux-category-shell,.stalux-link-shell{position:relative}.stalux-post-card,.stalux-category-card,.stalux-link-card,.stalux-word-card{transition:transform .2s,background-color .2s,border-color .2s,box-shadow .2s}:is(.stalux-post-card-shell,.stalux-category-shell):focus-within>*,.stalux-link-shell:focus-visible>*{box-shadow:var(--card-box-shadow-hover)}:is(button,summary,[role=button]):focus-visible{outline:2px solid var(--stalux-accent-color);outline-offset:3px}@media (prefers-reduced-motion:reduce){.stalux-main *,.stalux-main :before,.stalux-main :after{transition-duration:0s!important;animation:none!important}.stalux-post-card,.stalux-category-card,.stalux-link-card,.stalux-link-card-content,.stalux-card-post-cover img,.stalux-category-icon,.stalux-author-figure img,.stalux-link-card-avatar img{transform:none!important}}@keyframes stalux-social-stalux-social-arrive{0%{opacity:0;transform:translateY(.75rem)rotate(-12deg)}to{opacity:1;transform:translateY(0)rotate(0)}}@keyframes stalux-random-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes stalux-link-avatar-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}}@layer utilities{.sr-only{clip:rect(0,0,0,0);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.visible{visibility:visible}.absolute{position:absolute}.static{position:static}.grid{display:grid}.block{display:block}.inline-block{display:inline-block}.hidden{display:none}.h-full{height:100%}.flex{display:flex}.flex-shrink{flex-shrink:1}.flex-wrap{flex-wrap:wrap}.transform{transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z))}.b,.border{border-width:1px}.text-inherit{color:inherit}.no-underline{text-decoration:none}.outline{outline-style:solid}}@layer theme;@layer base{html,body{color:#fff;width:100%;height:100%;margin:0;& main{flex:1}scrollbar-color:#4a4a4a #2a2a2a;font-family:var(--font-body), "Noto Sans SC", "Noto Sans CJK SC", system-ui, -apple-system, sans-serif;text-rendering:optimizelegibility;line-height:1.25;display:block}body{height:auto;min-height:100%}html{scrollbar-gutter:stable;background-color:var(--stalux-bg-color,#202020)}html[data-clarity-id]{opacity:1!important}a{color:inherit;cursor:pointer;text-decoration-skip-ink:auto;background-color:#0000;text-decoration:none;transition:color .2s,text-shadow .2s;position:relative}.a-none{cursor:pointer;text-decoration-skip-ink:auto;background-color:#0000;color:inherit!important;text-decoration:none!important}.a-none:after{display:none!important}a:not(.a-none):not([data-stalux-card]):not(.stalux-nav-link):after{content:"";background:linear-gradient(90deg, var(--stalux-accent-color), var(--accent-50p));transform-origin:0;pointer-events:none;width:100%;height:1px;transition:transform .25s cubic-bezier(.25,.9,.2,1);position:absolute;bottom:-2px;left:0;transform:scaleX(0)}a:not(.a-none):not([data-stalux-card]):not(.stalux-nav-link):focus-visible:after,a:not(.a-none):not([data-stalux-card]):not(.stalux-nav-link):active:after{transform:scaleX(1)}@media (hover:hover) and (pointer:fine){a:not(.a-none):not([data-stalux-card]):not(.stalux-nav-link):hover:after{transform:scaleX(1)}}a:active,a:focus-visible{text-decoration:none}@media (hover:hover) and (pointer:fine){a:hover{text-decoration:none}}a:focus{outline:none}a:focus-visible{outline:2px solid var(--stalux-accent-color);outline-offset:2px}ul{margin:0;padding:0;list-style:none}.none{display:none}.stalux-root{color:#fff;min-height:100vh}.stalux-main{min-height:calc(100vh - var(--stalux-site-header-height,3rem))}:root{--stalux-bg-color:#202020;--stalux-bg-filter:none;--stalux-site-header-min-height:3.75rem;--stalux-site-header-height:var(--stalux-site-header-min-height);--stalux-text-shadow:var(--stalux-accent-color) .08rem .08rem .18rem;--stalux-accent-color:#eab308;--stalux-accent-rgb:234 179 8;--surface-glass:var(--black-05p);--surface-glass-shadow:var(--shadow-sidebar);--card-bg:var(--surface-glass);--card-border:#ffffff1a;--card-box-shadow:0 10px 24px #00000014;--card-box-shadow-hover:0 14px 28px #0000001f;--tag-bg:#ffffff1f;--tag-bg-hover:#ffffff2e;--category-bg:#ffffff14;--category-bg-hover:#ffffff24;--date-bg:#42a0ff2e;--date-border:#42a0ff40;--updated-bg:#ffc94733;--updated-border:#ffc9474d;--white-full:#fff;--white-95p:#fffffff2;--white-90p:#ffffffe6;--white-85p:#ffffffd9;--white-80p:#ffffffe6;--white-78p:#ffffffc7;--white-75p:#ffffffbf;--white-70p:#ffffffdb;--white-60p:#fff9;--white-50p:#ffffff80;--white-30p:#ffffff4d;--white-20p:#fff3;--white-15p:#ffffff26;--white-10p:#ffffff1a;--white-05p:#ffffff0d;--black-30p:#0000004d;--black-20p:#0003;--black-15p:#00000026;--black-10p:#0000001a;--black-05p:#0000000d;--accent-90p:rgb(var(--stalux-accent-rgb) / .9);--accent-85p:rgb(var(--stalux-accent-rgb) / .85);--accent-80p:rgb(var(--stalux-accent-rgb) / .8);--accent-70p:rgb(var(--stalux-accent-rgb) / .7);--accent-60p:rgb(var(--stalux-accent-rgb) / .6);--accent-50p:rgb(var(--stalux-accent-rgb) / .5);--accent-40p:rgb(var(--stalux-accent-rgb) / .4);--accent-30p:rgb(var(--stalux-accent-rgb) / .3);--accent-20p:rgb(var(--stalux-accent-rgb) / .2);--transition-fast:.18s ease;--transition-medium:.2s ease;--transition-normal:.25s ease;--transition-slow:.3s ease;--transition-slower:.5s ease;--transition-slowest:.6s ease;--easing-bounce:cubic-bezier(.175, .885, .32, 1.275);--space-2xs:.25rem;--space-xs:.75rem;--space-sm:.5rem;--space-md:1rem;--space-lg:1.5rem;--space-xl:2rem;--space-2xl:3rem;--space-3xl:4rem;--radius-sm:4px;--radius-md:6px;--radius-lg:8px;--radius-full:9999px;--shadow-sm:0 2px 5px #0000001a;--shadow-md:0 6px 16px #0003;--shadow-lg:0 8px 20px #0000004d;--shadow-xl:0 12px 26px #00000047;--shadow-sidebar:0 16px 20px -12px #000000d6, 10px 4px 16px -12px #00000094, -10px 4px 16px -12px #00000094;--shadow-raised-hover:0 20px 24px -13px #000000e6, 12px 5px 18px -13px #000000ad, -12px 5px 18px -13px #000000ad;--shadow-dark:#000000c0;--muted-text:#bbb;--font-body:"Noto Sans SC", "Noto Sans CJK SC", system-ui, sans-serif;--font-size-subtitle:1.35rem;--font-size-base:1.1rem;--font-size-small:.95rem;--font-size-smaller:.9rem;--font-size-tiny:.85rem;--border-thin:1px;--border-medium:2px;--border-thick:3px}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:#2a2a2a;border-radius:4px}::-webkit-scrollbar-thumb{background:#4a4a4a;border-radius:4px;transition:background .3s ease-out}@media (hover:hover) and (pointer:fine){::-webkit-scrollbar-thumb:hover{background:#5a5a5a}}::-webkit-scrollbar-corner{background:#2a2a2a}.bg-layer{z-index:-10;background-position:50%;background-repeat:repeat;background-size:auto;background-color:var(--stalux-bg-color,transparent);filter:var(--stalux-bg-filter,none);pointer-events:none;opacity:0;forced-color-adjust:none;color-scheme:light only;transition:opacity .2s ease-out;position:fixed;inset:0}@media (prefers-reduced-motion:reduce){.bg-layer{transition-duration:0s}}footer{padding:var(--space-lg) var(--space-md);text-align:center;color:var(--muted-text);border-top:1px solid var(--white-10p);backdrop-filter:blur(16px);background:linear-gradient(#0c0e122e,#0c0e12b8)}}@media print{[data-stalux-offscreen-code],[data-stalux-offscreen-svg]{content-visibility:visible!important;contain-intrinsic-block-size:none!important}}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}[data-stalux-home-content]{flex-direction:column;align-items:center;width:100%;display:flex}@keyframes stalux-page-exit{0%{opacity:1}to{opacity:1}}@keyframes stalux-page-enter{0%{opacity:.9}to{opacity:1}}@keyframes stalux-page-enter-back{0%{opacity:.96}to{opacity:1}}@keyframes stalux-taxonomy-enter{0%{opacity:.9}to{opacity:1}}@keyframes stalux-taxonomy-enter-back{0%{opacity:.96}to{opacity:1}}@keyframes stalux-article-enter{0%{opacity:.9}to{opacity:1}}@keyframes stalux-article-enter-back{0%{opacity:.96}to{opacity:1}}@keyframes stalux-sidebar-left-in{0%{opacity:.96}to{opacity:1}}@keyframes stalux-sidebar-left-out{0%{opacity:1}to{opacity:1}}@keyframes stalux-sidebar-left-back-in{0%{opacity:.96}to{opacity:1}}@keyframes stalux-sidebar-left-back-out{0%{opacity:1}to{opacity:1}}@keyframes stalux-sidebar-right-in{0%{opacity:.96}to{opacity:1}}@keyframes stalux-sidebar-right-out{0%{opacity:1}to{opacity:1}}@keyframes stalux-sidebar-right-back-in{0%{opacity:.96}to{opacity:1}}@keyframes stalux-sidebar-right-back-out{0%{opacity:1}to{opacity:1}}[data-stalux-scroll-sentinel]{visibility:hidden;pointer-events:none;width:1px;height:1px;position:absolute;top:240px;left:0}@media (prefers-reduced-motion:reduce){.stalux-main,.stalux-main *,.postLayout *{scroll-behavior:auto!important;transition-duration:1ms!important;animation-duration:1ms!important;animation-iteration-count:1!important}}
3
3
  /*$vite$:1*/
@@ -1,3 +1,4 @@
1
+ import { createHash } from "node:crypto";
1
2
  import type { AstroRuntimeLogger } from "astro";
2
3
  /**
3
4
  * 徽章生成器工具
@@ -56,13 +57,40 @@ export function generateBadge(options: BadgeOptions, logger?: AstroRuntimeLogger
56
57
  }
57
58
  }
58
59
 
59
- /**
60
- * 将SVG字符串转为Data URL
61
- * @param svg SVG字符串
62
- * @returns data URL
63
- */
60
+ /** Content-addressed assets are shared by all pages and remain lazy inside details. */
61
+ export function badgeAssetId(svg: string): string {
62
+ return createHash("sha256").update(svg).digest("hex").slice(0, 24);
63
+ }
64
+
65
+ export function normalizeBadgeStyle(style: string | undefined): BadgeOptions["style"] {
66
+ return style && ["flat", "plastic", "flat-square", "for-the-badge", "social"].includes(style)
67
+ ? (style as BadgeOptions["style"])
68
+ : "flat";
69
+ }
70
+
71
+ /** Keep Data URLs available for custom component badges outside footer.yml. */
64
72
  export function svgToDataUrl(svg: string): string {
65
73
  if (!svg) return "";
66
74
  const encoded = encodeURIComponent(svg).replaceAll("'", "%27").replaceAll('"', "%22");
67
75
  return `data:image/svg+xml,${encoded}`;
68
76
  }
77
+
78
+ type BadgeInput = Omit<BadgeOptions, "style"> & { style?: string };
79
+ type BadgeConfig = BadgeInput | { title: string; items: BadgeInput[] };
80
+ const assetCache = new WeakMap<readonly BadgeConfig[], Map<string, string>>();
81
+
82
+ /** Use identical normalization in the component and prerendered asset route. */
83
+ export function createBadgeAssets(items: readonly BadgeConfig[] | undefined): Map<string, string> {
84
+ if (!items) return new Map();
85
+ const cached = assetCache.get(items);
86
+ if (cached) return cached;
87
+ const assets = new Map<string, string>();
88
+ for (const item of items) {
89
+ for (const badge of "items" in item ? item.items : [item]) {
90
+ const svg = generateBadge({ ...badge, style: normalizeBadgeStyle(badge.style) });
91
+ assets.set(badgeAssetId(svg), svg);
92
+ }
93
+ }
94
+ assetCache.set(items, assets);
95
+ return assets;
96
+ }
@@ -16,6 +16,10 @@ export const ROUTE_DEFINITIONS: readonly RouteDefinition[] = [
16
16
  { pattern: "/tags/[tag]", source: "./pages/tags/[tag].astro" },
17
17
  { pattern: "/categories/", source: "./pages/categories/index.astro" },
18
18
  { pattern: "/categories/[category]", source: "./pages/categories/[category].astro" },
19
+ {
20
+ pattern: "/stalux-assets/badges/[id].svg",
21
+ source: "./pages/stalux-assets/badges/[id].svg.ts",
22
+ },
19
23
  { pattern: "/og/[image].[format]", source: "./pages/og/[image].[format].ts" },
20
24
  { pattern: "/rss.xml", source: "./pages/rss.xml.ts" },
21
25
  { pattern: "/atom.xml", source: "./pages/atom.xml.ts" },