@xingwangzhe/stalux 1.39.0 → 1.39.2

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 CHANGED
@@ -20,7 +20,7 @@ Code blocks default to the single `dark-plus` theme, with system color-scheme sw
20
20
 
21
21
  Route changes automatically show a 2px accent-colored progress bar at the top of the viewport, with no configuration. Its length follows actual milestones, with slow visual growth toward a bounded ceiling while waiting: loading begins, document/font preparation finishes, the DOM swaps, and page scripts finish; completion waits for the visual transition as well. It only reaches the end when loading and the transition both complete, and does not represent download/time percentages. It handles superseded or canceled navigation, history traversal, and reduced motion without additional requests or telemetry.
22
22
 
23
- The sticky header stays at the top while the body grows with long content. Route transitions use one content snapshot with a short crossfade and a stationary header. Nested titles, sidebars and paragraphs no longer create separate snapshots, reducing rasterization and compositing work on mobile devices. Content, lightboxes, search, comments and analytics keep their existing behavior.
23
+ The sticky header stays at the top while the body grows with long content. Route transitions use a 100ms native Web Animations API fade on the new content, with the persisted header and footer stationary. Page-sized View Transition snapshots and geometry interpolation are disabled; Astro still handles document preparation, history, scroll restoration and scripts. Progress and background updates wait for the real content animation to finish. Reduced motion and browser-provided history gestures skip this extra animation. Navigation batches layout reads in one frame and carries the persisted header's measured dimensions across swaps, reducing style recalculation, rasterization and compositing work on mobile devices. Content, lightboxes, search, comments and analytics keep their existing behavior.
24
24
 
25
25
  ---
26
26
 
@@ -195,11 +195,11 @@ id: head
195
195
  bingClarityId: "YOUR_CLARITY_PROJECT_ID"
196
196
  ```
197
197
 
198
- Google Analytics, Clarity, and Umami use low-priority native asynchronous scripts. Each SDK loads once per session, and failures do not block reading or navigation. Vercount rebinds visible counters after each `astro:page-load`. GA sends explicit pageviews. Umami disables automatic pageviews while retaining click tracking, capturing each page's URL, title, and referrer before the SDK resolves so rapid navigation cannot mix visits. An existing legacy Partytown session performs one full reload when it encounters the new runtime to prevent duplicate listeners and tracking.
198
+ Google Analytics, Clarity, and Umami use low-priority native asynchronous scripts. Each SDK loads once per session, and failures do not block reading or navigation. Vercount rebinds visible counters after each `astro:page-load`. GA captures each URL, title, and referrer, then sends pageviews in visit order through separate background tasks after the native transition releases rendering, the content animation is installed and a paint has been yielded. A bounded timer handles task starvation; hiding or leaving the document flushes pending views immediately. The SDK still runs on the main thread; this scheduling reduces synchronous work on the route's first frame. Umami disables automatic pageviews while retaining click tracking and captures page information before the SDK resolves so rapid navigation cannot mix visits. An existing older runtime or legacy Partytown session performs one full reload when it encounters the new runtime to prevent duplicate listeners and tracking.
199
199
 
200
200
  For Google Analytics, turn off **Page changes based on browser history events** under **Admin → Data streams → Enhanced measurement → Page views → Advanced settings**. Stalux sets `send_page_view: false` and sends the URL and title for each navigation explicitly. GA's history tracking must also be disabled in the property to prevent duplicate soft-navigation pageviews. Other enhanced measurement options can remain enabled. See [Google's pageview documentation](https://developers.google.com/analytics/devguides/collection/ga4/views).
201
201
 
202
- `bingClarityId` is the historical Stalux field name for a Microsoft Clarity Project ID. Get the ID from the Clarity project under **Settings → Setup → Get tracking code**. Stalux loads the tracking code asynchronously and preserves one loader across soft navigation. The SDK handles history navigation itself. Do not install the same project again through `anyhead`, a tag manager, or another plugin.
202
+ `bingClarityId` is the historical Stalux field name for a Microsoft Clarity Project ID. Get the ID from the Clarity project under **Settings → Setup → Get tracking code**. Stalux includes the official `@microsoft/clarity` npm dependency, imports it dynamically in the browser, and calls `Clarity.init()` once across soft navigation. Consumer projects such as myblog only configure the ID and upgrade the theme; no separate Clarity installation or script is needed. The theme observes tag loading failures and allows a later page to retry. Tag load, recorder readiness and successful collection are verified separately. The official npm package still loads the hosted recording engine and does not move DOM capture into a Worker. The SDK handles history navigation itself. Do not install the same project again through `anyhead`, a tag manager, or another plugin.
203
203
 
204
204
  The Project ID is a public browser identifier. Never put a Clarity Data Export API token in this YAML or in client-side code. If the site uses a strict CSP, consent banner, or CMP, configure those host-site policies and signals separately; this theme does not make legal compliance decisions for the site.
205
205
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xingwangzhe/stalux",
3
- "version": "1.39.0",
3
+ "version": "1.39.2",
4
4
  "description": "A powerful, modern Astro blog theme — use as template or install as plugin",
5
5
  "keywords": [
6
6
  "astro",
@@ -107,6 +107,7 @@
107
107
  "@expressive-code/plugin-line-numbers": "^0.44.2",
108
108
  "@lucide/astro": "^1.56.0",
109
109
  "@mcp-b/webmcp-polyfill": "^5.1.0",
110
+ "@microsoft/clarity": "1.0.2",
110
111
  "@pagefind/component-ui": "^1.5.2",
111
112
  "@waline/client": "^3.16.1",
112
113
  "@xingwangzhe/cjk-font-split-native": "^0.4.10",
@@ -134,7 +135,7 @@
134
135
  "@types/node": "^26.6.5",
135
136
  "@unocss/astro": "^66.11.0",
136
137
  "@vitest/coverage-v8": "^5.0.4",
137
- "astro": "^7.4.0",
138
+ "astro": "^7.4.1",
138
139
  "knip": "^6.41.0",
139
140
  "typescript": "npm:@typescript/typescript6@6.0.2",
140
141
  "unocss": "^66.11.0",
@@ -142,6 +143,6 @@
142
143
  "vitest": "^5.0.4"
143
144
  },
144
145
  "peerDependencies": {
145
- "astro": "^7.4.0"
146
+ "astro": "^7.4.1"
146
147
  }
147
148
  }
@@ -42,7 +42,7 @@ const ldJson = JSON.stringify(seo.schema).replace(/<\//g, "<\\/");
42
42
 
43
43
  <head>
44
44
  <meta charset="utf-8">
45
- <ClientRouter fallback="animate" />
45
+ <ClientRouter fallback="swap" />
46
46
  <SEO
47
47
  title={seo.title}
48
48
  description={seo.description}
@@ -288,48 +288,49 @@ export const styleNames: { classes: Record<string, string>; variables: Record<st
288
288
  "--stalux-bg-filter": "--s1o",
289
289
  "--stalux-nav-offset": "--s1p",
290
290
  "--stalux-site-header-height": "--s1q",
291
- "--stalux-text-shadow": "--s1r",
292
- "--surface-glass": "--s1s",
293
- "--surface-glass-shadow": "--s1t",
294
- "--tag-bg": "--s1u",
295
- "--tag-bg-hover": "--s1v",
296
- "--tl-line": "--s1w",
297
- "--tl-pad": "--s1x",
298
- "--transition-fast": "--s1y",
299
- "--transition-medium": "--s1z",
300
- "--transition-normal": "--s20",
301
- "--transition-slow": "--s21",
302
- "--transition-slower": "--s22",
303
- "--transition-slowest": "--s23",
304
- "--un-rotate": "--s24",
305
- "--un-rotate-x": "--s25",
306
- "--un-rotate-y": "--s26",
307
- "--un-rotate-z": "--s27",
308
- "--un-scale-x": "--s28",
309
- "--un-scale-y": "--s29",
310
- "--un-scale-z": "--s2a",
311
- "--un-skew-x": "--s2b",
312
- "--un-skew-y": "--s2c",
313
- "--un-translate-x": "--s2d",
314
- "--un-translate-y": "--s2e",
315
- "--un-translate-z": "--s2f",
316
- "--updated-bg": "--s2g",
317
- "--updated-border": "--s2h",
318
- "--white-05p": "--s2i",
319
- "--white-10p": "--s2j",
320
- "--white-15p": "--s2k",
321
- "--white-20p": "--s2l",
322
- "--white-30p": "--s2m",
323
- "--white-50p": "--s2n",
324
- "--white-60p": "--s2o",
325
- "--white-70p": "--s2p",
326
- "--white-75p": "--s2q",
327
- "--white-78p": "--s2r",
328
- "--white-80p": "--s2s",
329
- "--white-85p": "--s2t",
330
- "--white-90p": "--s2u",
331
- "--white-95p": "--s2v",
332
- "--white-full": "--s2w"
291
+ "--stalux-site-header-min-height": "--s1r",
292
+ "--stalux-text-shadow": "--s1s",
293
+ "--surface-glass": "--s1t",
294
+ "--surface-glass-shadow": "--s1u",
295
+ "--tag-bg": "--s1v",
296
+ "--tag-bg-hover": "--s1w",
297
+ "--tl-line": "--s1x",
298
+ "--tl-pad": "--s1y",
299
+ "--transition-fast": "--s1z",
300
+ "--transition-medium": "--s20",
301
+ "--transition-normal": "--s21",
302
+ "--transition-slow": "--s22",
303
+ "--transition-slower": "--s23",
304
+ "--transition-slowest": "--s24",
305
+ "--un-rotate": "--s25",
306
+ "--un-rotate-x": "--s26",
307
+ "--un-rotate-y": "--s27",
308
+ "--un-rotate-z": "--s28",
309
+ "--un-scale-x": "--s29",
310
+ "--un-scale-y": "--s2a",
311
+ "--un-scale-z": "--s2b",
312
+ "--un-skew-x": "--s2c",
313
+ "--un-skew-y": "--s2d",
314
+ "--un-translate-x": "--s2e",
315
+ "--un-translate-y": "--s2f",
316
+ "--un-translate-z": "--s2g",
317
+ "--updated-bg": "--s2h",
318
+ "--updated-border": "--s2i",
319
+ "--white-05p": "--s2j",
320
+ "--white-10p": "--s2k",
321
+ "--white-15p": "--s2l",
322
+ "--white-20p": "--s2m",
323
+ "--white-30p": "--s2n",
324
+ "--white-50p": "--s2o",
325
+ "--white-60p": "--s2p",
326
+ "--white-70p": "--s2q",
327
+ "--white-75p": "--s2r",
328
+ "--white-78p": "--s2s",
329
+ "--white-80p": "--s2t",
330
+ "--white-85p": "--s2u",
331
+ "--white-90p": "--s2v",
332
+ "--white-95p": "--s2w",
333
+ "--white-full": "--s2x"
333
334
  },
334
335
  "animations": {
335
336
  "fadeInUpComment": "k0",
@@ -342,19 +343,17 @@ export const styleNames: { classes: Record<string, string>; variables: Record<st
342
343
  "stalux-page-enter-back": "k7",
343
344
  "stalux-page-exit": "k8",
344
345
  "stalux-random-spin": "k9",
345
- "stalux-route-fade-in": "ka",
346
- "stalux-route-fade-out": "kb",
347
- "stalux-sidebar-left-back-in": "kc",
348
- "stalux-sidebar-left-back-out": "kd",
349
- "stalux-sidebar-left-in": "ke",
350
- "stalux-sidebar-left-out": "kf",
351
- "stalux-sidebar-right-back-in": "kg",
352
- "stalux-sidebar-right-back-out": "kh",
353
- "stalux-sidebar-right-in": "ki",
354
- "stalux-sidebar-right-out": "kj",
355
- "stalux-social-stalux-social-arrive": "kk",
356
- "stalux-taxonomy-enter": "kl",
357
- "stalux-taxonomy-enter-back": "km",
358
- "stalux-word-word-glow": "kn"
346
+ "stalux-sidebar-left-back-in": "ka",
347
+ "stalux-sidebar-left-back-out": "kb",
348
+ "stalux-sidebar-left-in": "kc",
349
+ "stalux-sidebar-left-out": "kd",
350
+ "stalux-sidebar-right-back-in": "ke",
351
+ "stalux-sidebar-right-back-out": "kf",
352
+ "stalux-sidebar-right-in": "kg",
353
+ "stalux-sidebar-right-out": "kh",
354
+ "stalux-social-stalux-social-arrive": "ki",
355
+ "stalux-taxonomy-enter": "kj",
356
+ "stalux-taxonomy-enter-back": "kk",
357
+ "stalux-word-word-glow": "kl"
359
358
  }
360
359
  };
@@ -12,7 +12,7 @@ import {
12
12
  getPromoteData,
13
13
  getSiteData,
14
14
  } from "@utils/config-utils";
15
- import { getRouteAnimation, getRouteKind } from "@utils/view-transitions";
15
+ import { getRouteKind } from "@utils/view-transitions";
16
16
 
17
17
  const props = Astro.props as Props;
18
18
 
@@ -49,11 +49,14 @@ const llmPromote = llmPromoteRaw
49
49
  ?.replace(/\{title\}/g, siteConfig.title)
50
50
  ?.replace(/\{cc\}/g, "CC-BY-NC-SA-4.0");
51
51
  const routeKind = getRouteKind(Astro.url.pathname);
52
- const routeAnimation = getRouteAnimation(routeKind);
53
52
  ---
54
53
 
55
54
  <!doctype html>
56
- <html lang={htmlLang} style={getAccentColorStyle(siteConfig.accentColor)}>
55
+ <html
56
+ lang={htmlLang}
57
+ style={getAccentColorStyle(siteConfig.accentColor)}
58
+ data-stalux-route={routeKind}
59
+ >
57
60
  <Head
58
61
  title={props.title}
59
62
  description={props.description}
@@ -118,7 +121,7 @@ const routeAnimation = getRouteAnimation(routeKind);
118
121
  class="stalux-main"
119
122
  data-stalux-route={routeKind}
120
123
  transition:name="stalux-page"
121
- transition:animate={routeAnimation}
124
+ transition:animate="none"
122
125
  >
123
126
  <slot />
124
127
  </main>
@@ -1,6 +1,7 @@
1
1
  /** Async native SDKs keep live DOM events out of worker proxies during Astro swaps. */
2
2
 
3
3
  import { createClientLogger } from "./logger";
4
+ import { prepareRouteMotion, type RouteMotionEvent } from "./route-motion";
4
5
 
5
6
  const logger = createClientLogger("analytics");
6
7
 
@@ -22,7 +23,7 @@ declare global {
22
23
  type AnalyticsHost = Window & {
23
24
  dataLayer?: unknown[];
24
25
  gtag?: (...args: unknown[]) => void;
25
- clarity?: ((...args: unknown[]) => void) & { q: unknown[] };
26
+ clarity?: ((...args: unknown[]) => void) & { v?: string };
26
27
  umami?: { track: (payload: Record<string, unknown>) => unknown };
27
28
  };
28
29
  const host = window as AnalyticsHost;
@@ -30,17 +31,101 @@ window.__staluxAnalyticsRuntime ??= {};
30
31
  const state = window.__staluxAnalyticsRuntime;
31
32
  state.scripts ??= new Map();
32
33
 
33
- /** Never await analytics in a router callback; failed loads can retry on a later page. */
34
- function loadTrackerScript(src: string, attributes: Record<string, string> = {}): Promise<void> {
35
- const existing = state.scripts?.get(src);
36
- if (existing) return existing;
37
- const script = document.createElement("script");
38
- script.async = true;
39
- script.fetchPriority = "low";
40
- script.src = src;
41
- for (const [name, value] of Object.entries(attributes)) script.setAttribute(name, value);
42
- const loaded = new Promise<void>((resolve, reject) => {
34
+ type GooglePage = {
35
+ ready: Promise<unknown>;
36
+ payload: {
37
+ page_location: string;
38
+ page_path: string;
39
+ page_title: string;
40
+ page_referrer: string;
41
+ };
42
+ };
43
+ const googlePages: GooglePage[] = [];
44
+ let transitionReady: Promise<unknown> = Promise.resolve();
45
+ let googleScheduled = false;
46
+ let googleSchedule = 0;
47
+ let googleTimer: ReturnType<typeof setTimeout> | undefined;
48
+ let googleFrames: number[] = [];
49
+
50
+ function cancelGoogleSchedule(): void {
51
+ googleSchedule++;
52
+ googleScheduled = false;
53
+ clearTimeout(googleTimer);
54
+ for (const frame of googleFrames) window.cancelAnimationFrame(frame);
55
+ googleFrames = [];
56
+ }
57
+
58
+ function sendGooglePage(page: GooglePage): void {
59
+ try {
60
+ host.gtag?.("event", "page_view", page.payload);
61
+ } catch {
62
+ logger.debug("Google page view unavailable; navigation remains usable");
63
+ }
64
+ }
65
+
66
+ /** The loaded Google SDK processes dataLayer.push synchronously and can read layout. */
67
+ function scheduleGooglePages(): void {
68
+ const page = googlePages[0];
69
+ if (!page || googleScheduled) return;
70
+ googleScheduled = true;
71
+ const schedule = ++googleSchedule;
72
+ const run = () => {
73
+ if (schedule !== googleSchedule) return;
74
+ cancelGoogleSchedule();
75
+ const next = googlePages.shift();
76
+ if (next) sendGooglePage(next);
77
+ // A burst of visits stays FIFO, with a separate task for each SDK call.
78
+ scheduleGooglePages();
79
+ };
80
+ // A queued visit can outlive its own transition. Also wait for whichever
81
+ // incoming snapshot is current when this task is scheduled.
82
+ void Promise.all([page.ready, transitionReady]).then(() => {
83
+ if (schedule !== googleSchedule) return;
84
+ // Let the routed content paint after native rendering resumes and its own
85
+ // motion is installed. The timer also covers throttled/background frames.
86
+ googleTimer = setTimeout(run, 1000);
87
+ googleFrames.push(
88
+ window.requestAnimationFrame(() => {
89
+ if (schedule !== googleSchedule) return;
90
+ googleFrames.push(
91
+ window.requestAnimationFrame(() => {
92
+ if (schedule !== googleSchedule) return;
93
+ const scheduler = (
94
+ window as Window & {
95
+ scheduler?: {
96
+ postTask: (
97
+ callback: () => void,
98
+ options: { priority: "background" },
99
+ ) => Promise<unknown>;
100
+ };
101
+ }
102
+ ).scheduler;
103
+ if (scheduler?.postTask) {
104
+ void scheduler.postTask(run, { priority: "background" }).catch(() => {
105
+ if (schedule === googleSchedule) setTimeout(run, 0);
106
+ });
107
+ } else setTimeout(run, 0);
108
+ }),
109
+ );
110
+ }),
111
+ );
112
+ });
113
+ }
114
+
115
+ function flushGooglePages(): void {
116
+ cancelGoogleSchedule();
117
+ // Native gtag remains on the main thread for GA's own pagehide/engagement work.
118
+ // Do not leave a queued view behind when the document is hidden or unloaded.
119
+ for (const page of googlePages.splice(0)) sendGooglePage(page);
120
+ }
121
+
122
+ /** Monitor only a script created by this integration; a loaded tag stays intact. */
123
+ function monitorTrackerScript(script: HTMLScriptElement, append = false): Promise<void> {
124
+ return new Promise<void>((resolve, reject) => {
125
+ let settled = false;
43
126
  const finish = (error?: Error) => {
127
+ if (settled) return;
128
+ settled = true;
44
129
  clearTimeout(timer);
45
130
  script.onload = null;
46
131
  script.onerror = null;
@@ -52,8 +137,20 @@ function loadTrackerScript(src: string, attributes: Record<string, string> = {})
52
137
  const timer = setTimeout(() => finish(new Error("Analytics SDK load timed out")), 15_000);
53
138
  script.onload = () => finish();
54
139
  script.onerror = () => finish(new Error("Analytics SDK load failed"));
55
- document.head.appendChild(script);
140
+ if (append) document.head.appendChild(script);
56
141
  });
142
+ }
143
+
144
+ /** Never await analytics in a router callback; failed loads can retry on a later page. */
145
+ function loadTrackerScript(src: string, attributes: Record<string, string> = {}): Promise<void> {
146
+ const existing = state.scripts?.get(src);
147
+ if (existing) return existing;
148
+ const script = document.createElement("script");
149
+ script.async = true;
150
+ script.fetchPriority = "low";
151
+ script.src = src;
152
+ for (const [name, value] of Object.entries(attributes)) script.setAttribute(name, value);
153
+ const loaded = monitorTrackerScript(script, true);
57
154
  state.scripts?.set(src, loaded);
58
155
  void loaded.catch(() => {
59
156
  if (state.scripts?.get(src) === loaded) state.scripts.delete(src);
@@ -61,6 +158,50 @@ function loadTrackerScript(src: string, attributes: Record<string, string> = {})
61
158
  return loaded;
62
159
  }
63
160
 
161
+ const CLARITY_SCRIPT_ID = "clarity-script";
162
+ const CLARITY_LOADER_KEY = "clarity:npm";
163
+
164
+ function hasExistingClarity(): boolean {
165
+ return Boolean(
166
+ host.clarity?.v ||
167
+ document.getElementById(CLARITY_SCRIPT_ID) ||
168
+ document.querySelector(
169
+ 'script[data-stalux-clarity],script[src^="https://www.clarity.ms/tag/"]',
170
+ ),
171
+ );
172
+ }
173
+
174
+ function initializeClarity(id: string): void {
175
+ if (state.clarityId === id || state.scripts?.has(CLARITY_LOADER_KEY)) return;
176
+ state.clarityId = id;
177
+ // A preceding SDK or user-provided tag owns its own lifecycle. Never create
178
+ // a second loader or attach destructive retry handlers to that script.
179
+ if (hasExistingClarity()) return;
180
+ const initialized = import("@microsoft/clarity").then(({ default: clarity }) => {
181
+ // Another integration may have installed its tag while this chunk loaded.
182
+ if (hasExistingClarity()) return;
183
+ clarity.init(id);
184
+ const script = document.getElementById(CLARITY_SCRIPT_ID);
185
+ // Official init returns void and catches its own exceptions. Its inserted
186
+ // element is the observable loader, not the return value of init().
187
+ if (script?.tagName !== "SCRIPT") {
188
+ throw new Error("Clarity did not create its loader");
189
+ }
190
+ const tracker = script as HTMLScriptElement;
191
+ tracker.dataset.staluxClarity = "true";
192
+ tracker.fetchPriority = "low";
193
+ // Tag load does not establish that its remote core or collector is ready.
194
+ return monitorTrackerScript(tracker);
195
+ });
196
+ state.scripts?.set(CLARITY_LOADER_KEY, initialized);
197
+ void initialized.catch(() => {
198
+ if (state.scripts?.get(CLARITY_LOADER_KEY) !== initialized) return;
199
+ state.scripts.delete(CLARITY_LOADER_KEY);
200
+ if (state.clarityId === id) state.clarityId = undefined;
201
+ logger.debug("Clarity loader unavailable; a later navigation can retry");
202
+ });
203
+ }
204
+
64
205
  function initializeTrackers(): void {
65
206
  const config = document.body?.dataset;
66
207
  if (!config) return;
@@ -82,32 +223,23 @@ function initializeTrackers(): void {
82
223
  { "data-stalux-google": "true" },
83
224
  ).catch(() => logger.debug("Google SDK unavailable; queued events retained"));
84
225
  }
85
- if (config.staluxClarityId && state.clarityId !== config.staluxClarityId) {
86
- host.clarity ??= Object.assign(
87
- function (..._args: unknown[]) {
88
- // biome-ignore lint/complexity/noArguments: Match the official Clarity queue format.
89
- host.clarity?.q.push(arguments);
90
- },
91
- { q: [] as unknown[] },
92
- );
93
- const id = config.staluxClarityId;
94
- state.clarityId = id;
95
- void loadTrackerScript(`https://www.clarity.ms/tag/${encodeURIComponent(id)}`, {
96
- "data-stalux-clarity": "true",
97
- }).catch(() => {
98
- if (state.clarityId === id) state.clarityId = undefined;
99
- logger.debug("Clarity SDK unavailable; a later navigation can retry");
100
- });
101
- }
226
+ if (config.staluxClarityId) initializeClarity(config.staluxClarityId);
102
227
  }
103
228
 
104
229
  function trackGooglePage(): void {
105
230
  if (!state.googleId) return;
106
- host.gtag?.("event", "page_view", {
107
- page_location: window.location.href,
108
- page_path: window.location.pathname + window.location.search,
109
- page_title: document.title,
231
+ // Capture the visit before yielding: another navigation must not replace its
232
+ // URL, title or virtual referrer while the SDK is waiting for its task.
233
+ googlePages.push({
234
+ ready: transitionReady,
235
+ payload: {
236
+ page_location: window.location.href,
237
+ page_path: window.location.pathname + window.location.search,
238
+ page_title: document.title,
239
+ page_referrer: state.previousPage ?? document.referrer,
240
+ },
110
241
  });
242
+ scheduleGooglePages();
111
243
  }
112
244
 
113
245
  function trackUmamiPage(): void {
@@ -142,15 +274,26 @@ function trackVercountPage(): void {
142
274
  document.head.appendChild(script);
143
275
  }
144
276
 
145
- if (state.installed && (!state.native || state.revision !== 3)) {
146
- // Revision 3 also replaces the shared navigation/motion listeners.
277
+ if (state.installed && (!state.native || state.revision !== 4)) {
278
+ // Revision 4 replaces synchronous Google page views and the motion policy.
147
279
  // An open tab can cross a deployment boundary. Its old anonymous worker
148
280
  // listeners cannot be detached safely; reload once to install the new runtime.
149
281
  window.location.reload();
150
282
  } else if (!state.installed) {
151
283
  state.native = true;
152
- state.revision = 3;
284
+ state.revision = 4;
153
285
  state.installed = true;
286
+ document.addEventListener("astro:before-swap", (event) => {
287
+ transitionReady = prepareRouteMotion(event as Event & RouteMotionEvent).ready;
288
+ // Invalidate an older page's frames, timer and posted task before this
289
+ // incoming snapshot starts. Its captured visit remains in the FIFO.
290
+ cancelGoogleSchedule();
291
+ scheduleGooglePages();
292
+ });
293
+ window.addEventListener("pagehide", flushGooglePages);
294
+ document.addEventListener("visibilitychange", () => {
295
+ if (document.hidden) flushGooglePages();
296
+ });
154
297
  document.addEventListener("astro:page-load", () => {
155
298
  initializeTrackers();
156
299
  trackGooglePage();
@@ -13,6 +13,7 @@
13
13
  * 先低优先级解码目标背景,等待内容过渡完成后再淡入,不与内容快照争抢绘制。
14
14
  */
15
15
  import { createClientLogger } from "./logger";
16
+ import { prepareRouteMotion, type RouteMotionEvent } from "./route-motion";
16
17
 
17
18
  const logger = createClientLogger("background");
18
19
 
@@ -115,8 +116,7 @@ if (!(window as Window & { __STALUX_BG_LISTENER__?: boolean }).__STALUX_BG_LISTE
115
116
  prepared = connection?.saveData ? undefined : prepareBackground();
116
117
  });
117
118
  document.addEventListener("astro:before-swap", (event) => {
118
- const transition = (event as Event & { viewTransition?: ViewTransition }).viewTransition;
119
- transitionFinished = transition?.finished.catch(() => undefined) ?? Promise.resolve();
119
+ transitionFinished = prepareRouteMotion(event as RouteMotionEvent).finished;
120
120
  });
121
121
  document.addEventListener("astro:page-load", initializeBackground);
122
122
  }
@@ -2,6 +2,7 @@ import type {
2
2
  TransitionBeforePreparationEvent,
3
3
  TransitionBeforeSwapEvent,
4
4
  } from "astro:transitions/client";
5
+ import { prepareRouteMotion } from "./route-motion";
5
6
 
6
7
  type Stage = "loading" | "prepared" | "swapped" | "loaded" | "complete";
7
8
  interface Navigation {
@@ -125,7 +126,7 @@ export function installNavigationProgress(): void {
125
126
  complete(navigation);
126
127
  };
127
128
  // Skipped/rejected animations must not leave a completed navigation stuck.
128
- void swap.viewTransition.finished.then(finished, finished);
129
+ void prepareRouteMotion(swap).finished.then(finished);
129
130
  });
130
131
  document.addEventListener("astro:after-swap", () => {
131
132
  if (!active || swapping !== active || active.signal.aborted) return;
@@ -43,26 +43,19 @@ registerPageLifecycle("navigation", () => {
43
43
  const controller = new AbortController();
44
44
  const listenerOptions = { signal: controller.signal };
45
45
  let frameId = 0;
46
- let lastScale = "1";
46
+ let lastScale = navList.style.getPropertyValue("--nav-scale") || "1";
47
+ let needsButtons = true;
48
+ let needsSurface = true;
49
+ let needsResize = false;
47
50
  let needsScale = true;
48
51
  const mobileNav = window.matchMedia("(max-width: 600px)");
49
52
 
50
- const updateNavSurface = () => {
51
- siteNav?.toggleAttribute("data-scrolled", window.scrollY > 24);
52
- navToggle?.toggleAttribute("data-scrolled", window.scrollY > 24);
53
- };
54
- window.addEventListener("scroll", updateNavSurface, {
55
- passive: true,
56
- signal: controller.signal,
57
- });
58
- updateNavSurface();
59
-
60
53
  const closeNav = () => {
61
54
  navList.removeAttribute("data-state");
62
55
  navToggle?.removeAttribute("data-state");
63
56
  navToggle?.setAttribute("aria-expanded", "false");
64
57
  navOverlay?.removeAttribute("data-state");
65
- document.body.style.overflow = "";
58
+ if (document.body.style.overflow) document.body.style.overflow = "";
66
59
  };
67
60
  const openNav = () => {
68
61
  navList.dataset.state = "open";
@@ -73,26 +66,17 @@ registerPageLifecycle("navigation", () => {
73
66
  };
74
67
  const updateButtons = () => {
75
68
  frameId = 0;
76
- // Read layout before writing styles or attributes. Scrolling does not
77
- // change item sizes, so only resize events need to recalculate scale.
78
- const { clientWidth, scrollWidth, scrollLeft } = navList;
79
- const headerHeight = siteHeader?.offsetHeight ?? 0;
80
- const headerHeightValue = `${headerHeight}px`;
81
- if (
82
- siteHeader &&
83
- document.documentElement.style.getPropertyValue("--stalux-site-header-height") !==
84
- headerHeightValue
85
- ) {
86
- document.documentElement.style.setProperty(
87
- "--stalux-site-header-height",
88
- headerHeightValue,
89
- );
90
- }
91
- const navOffset = headerHeight + 16;
92
- document.documentElement.style.setProperty("--stalux-nav-offset", `${navOffset}px`);
69
+ // Collect every layout read before changing attributes or inherited
70
+ // root variables. Even scrollY can force style calculation after a swap.
71
+ const scrolled = needsSurface ? window.scrollY > 24 : undefined;
72
+ const shouldCloseNav = needsResize && !mobileNav.matches;
73
+ const updateOverflow = needsButtons;
74
+ const clientWidth = updateOverflow ? navList.clientWidth : 0;
75
+ const scrollWidth = updateOverflow ? navList.scrollWidth : 0;
76
+ const scrollLeft = updateOverflow ? navList.scrollLeft : 0;
77
+ const headerHeight = updateOverflow ? (siteHeader?.offsetHeight ?? 0) : 0;
93
78
  let nextScale = lastScale;
94
79
  if (needsScale) {
95
- needsScale = false;
96
80
  if (mobileNav.matches) {
97
81
  nextScale = "1";
98
82
  } else {
@@ -112,25 +96,60 @@ registerPageLifecycle("navigation", () => {
112
96
  );
113
97
  }
114
98
  }
99
+ needsButtons = false;
100
+ needsSurface = false;
101
+ needsResize = false;
102
+ needsScale = false;
103
+
104
+ if (updateOverflow) {
105
+ const rootStyle = document.documentElement.style;
106
+ const headerHeightValue = `${headerHeight}px`;
107
+ if (
108
+ siteHeader &&
109
+ rootStyle.getPropertyValue("--stalux-site-header-height") !== headerHeightValue
110
+ ) {
111
+ rootStyle.setProperty("--stalux-site-header-height", headerHeightValue);
112
+ }
113
+ const navOffsetValue = `${headerHeight + 16}px`;
114
+ if (rootStyle.getPropertyValue("--stalux-nav-offset") !== navOffsetValue) {
115
+ rootStyle.setProperty("--stalux-nav-offset", navOffsetValue);
116
+ }
117
+ }
115
118
  if (nextScale !== lastScale) {
116
119
  navList.style.setProperty("--nav-scale", nextScale);
117
120
  lastScale = nextScale;
118
121
  }
119
- const hasOverflow = scrollWidth > clientWidth;
120
- buttonLeft.toggleAttribute("data-visible", hasOverflow && scrollLeft > 1);
121
- buttonRight.toggleAttribute(
122
- "data-visible",
123
- hasOverflow && scrollLeft + clientWidth < scrollWidth - 1,
124
- );
122
+ if (scrolled !== undefined) {
123
+ siteNav?.toggleAttribute("data-scrolled", scrolled);
124
+ navToggle?.toggleAttribute("data-scrolled", scrolled);
125
+ }
126
+ if (updateOverflow) {
127
+ const hasOverflow = scrollWidth > clientWidth;
128
+ buttonLeft.toggleAttribute("data-visible", hasOverflow && scrollLeft > 1);
129
+ buttonRight.toggleAttribute(
130
+ "data-visible",
131
+ hasOverflow && scrollLeft + clientWidth < scrollWidth - 1,
132
+ );
133
+ }
134
+ if (shouldCloseNav) closeNav();
125
135
  };
126
- const scheduleUpdate = () => {
136
+ const scheduleFrame = () => {
127
137
  if (!frameId) frameId = requestAnimationFrame(updateButtons);
128
138
  };
139
+ const scheduleUpdate = () => {
140
+ needsButtons = true;
141
+ scheduleFrame();
142
+ };
143
+ const scheduleSurface = () => {
144
+ needsSurface = true;
145
+ scheduleFrame();
146
+ };
129
147
  const scheduleResize = () => {
130
- if (!mobileNav.matches) closeNav();
131
- updateNavSurface();
148
+ needsButtons = true;
149
+ needsSurface = true;
150
+ needsResize = true;
132
151
  needsScale = true;
133
- scheduleUpdate();
152
+ scheduleFrame();
134
153
  };
135
154
 
136
155
  document.querySelector("[data-ref='stalux-search-btn']")?.addEventListener(
@@ -167,6 +186,10 @@ registerPageLifecycle("navigation", () => {
167
186
  passive: true,
168
187
  signal: controller.signal,
169
188
  });
189
+ window.addEventListener("scroll", scheduleSurface, {
190
+ passive: true,
191
+ signal: controller.signal,
192
+ });
170
193
  window.addEventListener("resize", scheduleResize, {
171
194
  passive: true,
172
195
  signal: controller.signal,
@@ -186,7 +209,7 @@ registerPageLifecycle("navigation", () => {
186
209
  resizeObserver.disconnect();
187
210
  if (frameId) cancelAnimationFrame(frameId);
188
211
  closeNav();
189
- document.documentElement.style.removeProperty("--stalux-nav-offset");
190
- document.documentElement.style.removeProperty("--stalux-site-header-height");
212
+ // Keep --stalux-nav-offset and --stalux-site-header-height for the
213
+ // persisted header, avoiding inherited-style invalidation per page.
191
214
  };
192
215
  });
@@ -0,0 +1,108 @@
1
+ export interface RouteMotionEvent {
2
+ viewTransition?: {
3
+ ready?: PromiseLike<unknown>;
4
+ finished?: PromiseLike<unknown>;
5
+ };
6
+ signal?: AbortSignal;
7
+ }
8
+
9
+ export interface RouteMotion {
10
+ ready: Promise<void>;
11
+ finished: Promise<void>;
12
+ start: (main: HTMLElement | null, options?: { skip?: boolean }) => void;
13
+ cancel: () => void;
14
+ }
15
+
16
+ const motions = new WeakMap<object, RouteMotion>();
17
+ const settle = (promise?: PromiseLike<unknown>): Promise<void> =>
18
+ Promise.resolve(promise).then(
19
+ () => undefined,
20
+ () => undefined,
21
+ );
22
+
23
+ function completion() {
24
+ let resolve = () => {};
25
+ const promise = new Promise<void>((done) => {
26
+ resolve = done;
27
+ });
28
+ return { promise, resolve };
29
+ }
30
+
31
+ /** Share the native swap and the real content animation across all route consumers. */
32
+ export function prepareRouteMotion(event: RouteMotionEvent): RouteMotion {
33
+ const native = event.viewTransition;
34
+ const key = native ?? event;
35
+ const existing = motions.get(key);
36
+ if (existing) return existing;
37
+
38
+ // Create these before swap: a consumer must not observe native.finished and
39
+ // mistake the gap before after-swap installs WAAPI for completed motion.
40
+ const installed = completion();
41
+ const animationDone = completion();
42
+ const canceled = completion();
43
+ let animation: Animation | undefined;
44
+ let started = false;
45
+ let disposed = false;
46
+ const clearAnimation = () => {
47
+ const active = animation;
48
+ animation = undefined;
49
+ try {
50
+ active?.cancel();
51
+ } catch {
52
+ // Releasing an already detached effect must not hold up navigation.
53
+ }
54
+ animationDone.resolve();
55
+ };
56
+ const cancel = () => {
57
+ if (disposed) return;
58
+ disposed = true;
59
+ started = true;
60
+ clearAnimation();
61
+ installed.resolve();
62
+ canceled.resolve();
63
+ event.signal?.removeEventListener("abort", cancel);
64
+ };
65
+ const ready = Promise.race([
66
+ canceled.promise,
67
+ Promise.all([settle(native?.ready), installed.promise]).then(() => undefined),
68
+ ]);
69
+ const finished = Promise.race([
70
+ canceled.promise,
71
+ Promise.all([settle(native?.finished), animationDone.promise]).then(() => undefined),
72
+ ]);
73
+ const motion: RouteMotion = {
74
+ ready,
75
+ finished,
76
+ start(main, options) {
77
+ if (started) return;
78
+ started = true;
79
+ try {
80
+ if (
81
+ !main ||
82
+ options?.skip ||
83
+ document.hidden ||
84
+ window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ||
85
+ typeof main.animate !== "function"
86
+ ) {
87
+ animationDone.resolve();
88
+ return;
89
+ }
90
+ animation = main.animate(
91
+ { opacity: [0.7, 1] },
92
+ { duration: 100, easing: "ease-out" },
93
+ );
94
+ void animation.finished.then(clearAnimation, clearAnimation);
95
+ } catch {
96
+ clearAnimation();
97
+ } finally {
98
+ installed.resolve();
99
+ }
100
+ },
101
+ cancel,
102
+ };
103
+ motions.set(key, motion);
104
+ if (event.signal?.aborted) cancel();
105
+ else event.signal?.addEventListener("abort", cancel, { once: true });
106
+ void finished.then(() => event.signal?.removeEventListener("abort", cancel));
107
+ return motion;
108
+ }
@@ -2,6 +2,7 @@ import { getRouteKind } from "@utils/public-routes";
2
2
  import { createClientLogger } from "./logger";
3
3
  import { prepareNavigationFont } from "./navigation-font";
4
4
  import { installNavigationProgress } from "./navigation-progress";
5
+ import { prepareRouteMotion } from "./route-motion";
5
6
 
6
7
  const logger = createClientLogger("view-transitions");
7
8
 
@@ -13,14 +14,18 @@ function setRouteState(pathname: string): void {
13
14
  const html = document.documentElement;
14
15
  const route = getRouteKind(pathname);
15
16
  logger.debug(`route state=${route}`);
16
- html.dataset.staluxRoute = route;
17
- document.querySelector("main.stalux-main")?.setAttribute("data-stalux-route", route);
17
+ if (html.dataset.staluxRoute !== route) html.dataset.staluxRoute = route;
18
+ const main = document.querySelector("main[data-stalux-route]");
19
+ if (main && main.getAttribute("data-stalux-route") !== route) {
20
+ main.setAttribute("data-stalux-route", route);
21
+ }
18
22
  }
19
23
 
20
24
  interface NavigationEvent {
21
25
  from: URL;
22
26
  to: URL;
23
27
  direction: string;
28
+ navigationType?: string;
24
29
  loader: () => Promise<void>;
25
30
  newDocument: Document;
26
31
  signal: AbortSignal;
@@ -41,14 +46,43 @@ function clearNavigationState(): void {
41
46
  delete document.documentElement.dataset.staluxVtDirection;
42
47
  }
43
48
 
49
+ function preserveHeaderMetrics(nextDocument: Document): void {
50
+ const selector = '[data-astro-transition-persist="stalux-site-header"]';
51
+ if (!document.querySelector(selector) || !nextDocument.querySelector(selector)) return;
52
+ // The header DOM persists. Keep its measured dimensions through Astro's root
53
+ // attribute swap instead of invalidating inherited layout variables twice.
54
+ for (const property of ["--stalux-nav-offset", "--stalux-site-header-height"]) {
55
+ const value = document.documentElement.style.getPropertyValue(property);
56
+ if (value && !nextDocument.documentElement.style.getPropertyValue(property)) {
57
+ nextDocument.documentElement.style.setProperty(property, value);
58
+ }
59
+ }
60
+ }
61
+
44
62
  const transitionWindow = window as TransitionWindow;
45
63
  if (!transitionWindow.__STALUX_VT_LISTENER__) {
46
64
  transitionWindow.__STALUX_VT_LISTENER__ = true;
47
65
 
48
66
  let generation = 0;
67
+ let motion: ReturnType<typeof prepareRouteMotion> | undefined;
68
+ let pendingBrowserMotion = false;
69
+ let browserMotion = false;
70
+ window.addEventListener(
71
+ "popstate",
72
+ (event) => {
73
+ pendingBrowserMotion =
74
+ (event as PopStateEvent & { hasUAVisualTransition?: boolean })
75
+ .hasUAVisualTransition === true;
76
+ },
77
+ { capture: true },
78
+ );
49
79
  document.addEventListener("astro:before-preparation", (event) => {
50
80
  const navigation = event as unknown as NavigationEvent;
51
81
  const current = ++generation;
82
+ motion?.cancel();
83
+ motion = undefined;
84
+ browserMotion = pendingBrowserMotion && navigation.navigationType === "traverse";
85
+ pendingBrowserMotion = false;
52
86
  setNavigationState(navigation);
53
87
  navigation.signal.addEventListener(
54
88
  "abort",
@@ -73,17 +107,24 @@ if (!transitionWindow.__STALUX_VT_LISTENER__) {
73
107
  const navigation = event as unknown as NavigationEvent;
74
108
  if (navigation.signal.aborted) return;
75
109
  const current = generation;
110
+ motion = prepareRouteMotion(navigation);
111
+ preserveHeaderMetrics(navigation.newDocument);
76
112
  // Astro replaces the root attributes during swap. Carry motion state into
77
113
  // the new document and keep it until the last animation actually settles.
78
114
  setNavigationState(navigation, navigation.newDocument.documentElement);
79
115
  const finish = () => {
80
116
  if (current === generation) clearNavigationState();
81
117
  };
82
- void navigation.viewTransition?.finished.then(finish, finish);
118
+ void motion.finished.then(finish, finish);
83
119
  });
84
120
 
85
121
  document.addEventListener("astro:after-swap", () => {
86
122
  setRouteState(window.location.pathname);
123
+ // Install the effect before the first new-content paint. The browser
124
+ // starts its timeline after the native transition releases rendering.
125
+ motion?.start(document.querySelector<HTMLElement>("main[data-stalux-route]"), {
126
+ skip: browserMotion,
127
+ });
87
128
  });
88
129
 
89
130
  document.addEventListener("astro:page-load", () => {
@@ -131,7 +131,8 @@
131
131
  :root {
132
132
  --stalux-bg-color: #202020;
133
133
  --stalux-bg-filter: none;
134
- --stalux-site-header-height: 3.75rem;
134
+ --stalux-site-header-min-height: 3.75rem;
135
+ --stalux-site-header-height: var(--stalux-site-header-min-height);
135
136
  --stalux-text-shadow: var(--stalux-accent-color) 0.08rem 0.08rem 0.18rem;
136
137
  --stalux-accent-color: #eab308;
137
138
  --stalux-accent-rgb: 234 179 8;
@@ -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), box-shadow 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-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 .65s ease-in-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{animation:.65s ease-in-out stalux-link-avatar-spin}.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,box-shadow .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-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)}}@keyframes stalux-word-word-glow{0%{box-shadow:0 0 30px var(--accent-50p), 0 0 80px var(--accent-30p), var(--shadow-lg);transform:scale(1.02)}to{box-shadow:0 0 20px var(--accent-40p), 0 0 60px var(--accent-20p), var(--shadow-lg);transform:scale(1)}}}@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}.fixed{position:fixed}.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}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}}@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-height:3.75rem;--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 .65s ease-in-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)}}.stagger-children>:first-child{animation-delay:.1s}.stagger-children>:nth-child(2){animation-delay:.15s}.stagger-children>:nth-child(3){animation-delay:.2s}.stagger-children>:nth-child(4){animation-delay:.25s}.stagger-children>:nth-child(5){animation-delay:.3s}.stagger-children>:nth-child(n+6){animation-delay:.35s}@keyframes stalux-page-exit{to{opacity:0}}@keyframes stalux-page-enter{0%{opacity:0;transform:translateY(9px)}to{opacity:1;transform:translateY(0)}}@keyframes stalux-page-enter-back{0%{opacity:0;transform:translateY(-9px)}to{opacity:1;transform:translateY(0)}}@keyframes stalux-taxonomy-enter{0%{opacity:0;transform:translate(12px)}to{opacity:1;transform:translate(0)}}@keyframes stalux-taxonomy-enter-back{0%{opacity:0;transform:translate(-12px)}to{opacity:1;transform:translate(0)}}@keyframes stalux-article-enter{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}@keyframes stalux-article-enter-back{0%{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}@keyframes stalux-sidebar-left-in{0%{opacity:0;transform:translate(-14px)}to{opacity:1;transform:translate(0)}}@keyframes stalux-sidebar-left-out{to{opacity:0;transform:translate(-10px)}}@keyframes stalux-sidebar-left-back-in{0%{opacity:0;transform:translate(14px)}to{opacity:1;transform:translate(0)}}@keyframes stalux-sidebar-left-back-out{to{opacity:0;transform:translate(10px)}}@keyframes stalux-sidebar-right-in{0%{opacity:0;transform:translate(14px)}to{opacity:1;transform:translate(0)}}@keyframes stalux-sidebar-right-out{to{opacity:0;transform:translate(10px)}}@keyframes stalux-sidebar-right-back-in{0%{opacity:0;transform:translate(-14px)}to{opacity:1;transform:translate(0)}}@keyframes stalux-sidebar-right-back-out{to{opacity:0;transform:translate(-10px)}}.stalux-main *{view-transition-name:none!important}@keyframes stalux-route-fade-out{to{opacity:0}}@keyframes stalux-route-fade-in{0%{opacity:0}to{opacity:1}}::view-transition-old(stalux-page){animation:.1s ease-out both stalux-route-fade-out!important}::view-transition-new(stalux-page){animation:.16s ease-out both stalux-route-fade-in!important}::view-transition-group(stalux-page){z-index:1}::view-transition-group(stalux-article-page){z-index:1;animation:none}::view-transition-old(stalux-article-page),::view-transition-new(stalux-article-page){animation:none}html[data-stalux-vt-from=article][data-stalux-vt-to=article]::view-transition-group(left-sidebar),html[data-stalux-vt-from=article][data-stalux-vt-to=article]::view-transition-group(right-sidebar),html[data-stalux-vt-from=article][data-stalux-vt-to=article]::view-transition-old(left-sidebar),html[data-stalux-vt-from=article][data-stalux-vt-to=article]::view-transition-new(left-sidebar),html[data-stalux-vt-from=article][data-stalux-vt-to=article]::view-transition-old(right-sidebar),html[data-stalux-vt-from=article][data-stalux-vt-to=article]::view-transition-new(right-sidebar){animation:none!important}::view-transition-old(stalux-page),::view-transition-new(stalux-page){transform-origin:top}::view-transition-group(*){animation-duration:.24s;animation-timing-function:cubic-bezier(.22,.72,.22,1)}[data-astro-transition-persist=stalux-site-header]{view-transition-name:stalux-site-header}::view-transition-group(stalux-site-header){z-index:990;animation:none}::view-transition-old(stalux-site-header),::view-transition-new(stalux-site-header){animation:none}[data-astro-transition-persist=stalux-site-footer]{view-transition-name:stalux-site-footer}::view-transition-group(stalux-site-footer),::view-transition-old(stalux-site-footer),::view-transition-new(stalux-site-footer){animation:none}@media (prefers-reduced-motion:reduce){::view-transition-group(*),::view-transition-old(*),::view-transition-new(*){animation-duration:1ms!important;animation-delay:0s!important}.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-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), box-shadow 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 .65s ease-in-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{animation:.65s ease-in-out stalux-link-avatar-spin}.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,box-shadow .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)}}@keyframes stalux-word-word-glow{0%{box-shadow:0 0 30px var(--accent-50p), 0 0 80px var(--accent-30p), var(--shadow-lg);transform:scale(1.02)}to{box-shadow:0 0 20px var(--accent-40p), 0 0 60px var(--accent-20p), var(--shadow-lg);transform:scale(1)}}}@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}.fixed{position:fixed}.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}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}}@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 .65s ease-in-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)}}.stagger-children>:first-child{animation-delay:.1s}.stagger-children>:nth-child(2){animation-delay:.15s}.stagger-children>:nth-child(3){animation-delay:.2s}.stagger-children>:nth-child(4){animation-delay:.25s}.stagger-children>:nth-child(5){animation-delay:.3s}.stagger-children>:nth-child(n+6){animation-delay:.35s}@keyframes stalux-page-exit{to{opacity:0}}@keyframes stalux-page-enter{0%{opacity:0;transform:translateY(9px)}to{opacity:1;transform:translateY(0)}}@keyframes stalux-page-enter-back{0%{opacity:0;transform:translateY(-9px)}to{opacity:1;transform:translateY(0)}}@keyframes stalux-taxonomy-enter{0%{opacity:0;transform:translate(12px)}to{opacity:1;transform:translate(0)}}@keyframes stalux-taxonomy-enter-back{0%{opacity:0;transform:translate(-12px)}to{opacity:1;transform:translate(0)}}@keyframes stalux-article-enter{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}@keyframes stalux-article-enter-back{0%{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}@keyframes stalux-sidebar-left-in{0%{opacity:0;transform:translate(-14px)}to{opacity:1;transform:translate(0)}}@keyframes stalux-sidebar-left-out{to{opacity:0;transform:translate(-10px)}}@keyframes stalux-sidebar-left-back-in{0%{opacity:0;transform:translate(14px)}to{opacity:1;transform:translate(0)}}@keyframes stalux-sidebar-left-back-out{to{opacity:0;transform:translate(10px)}}@keyframes stalux-sidebar-right-in{0%{opacity:0;transform:translate(14px)}to{opacity:1;transform:translate(0)}}@keyframes stalux-sidebar-right-out{to{opacity:0;transform:translate(10px)}}@keyframes stalux-sidebar-right-back-in{0%{opacity:0;transform:translate(-14px)}to{opacity:1;transform:translate(0)}}@keyframes stalux-sidebar-right-back-out{to{opacity:0;transform:translate(-10px)}}html,.stalux-main,.stalux-main *,[data-astro-transition-persist]{view-transition-name:none!important}@media (prefers-reduced-motion:reduce){::view-transition-group(*),::view-transition-old(*),::view-transition-new(*){animation-duration:1ms!important;animation-delay:0s!important}.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*/
@@ -173,107 +173,16 @@
173
173
  }
174
174
  }
175
175
 
176
- /* One content snapshot per route. Nested titles, cards, sidebars and long
177
- article blocks otherwise become dozens of individually rasterized surfaces.
178
- Components used outside the shared shell retain their own transition metadata. */
179
- .stalux-main * {
176
+ /* The native router keeps document/history semantics; content motion uses a
177
+ short Web Animations API effect instead of page-sized snapshot surfaces.
178
+ Persisted shell elements remain live and stationary during the swap. */
179
+ html,
180
+ .stalux-main,
181
+ .stalux-main *,
182
+ [data-astro-transition-persist] {
180
183
  view-transition-name: none !important;
181
184
  }
182
185
 
183
- @keyframes stalux-route-fade-out {
184
- to {
185
- opacity: 0;
186
- }
187
- }
188
- @keyframes stalux-route-fade-in {
189
- from {
190
- opacity: 0;
191
- }
192
- to {
193
- opacity: 1;
194
- }
195
- }
196
- ::view-transition-old(stalux-page) {
197
- animation: stalux-route-fade-out 100ms ease-out both !important;
198
- }
199
- ::view-transition-new(stalux-page) {
200
- animation: stalux-route-fade-in 160ms ease-out both !important;
201
- }
202
-
203
- ::view-transition-group(stalux-page) {
204
- z-index: 1;
205
- }
206
-
207
- ::view-transition-group(stalux-article-page) {
208
- z-index: 1;
209
- animation: none;
210
- }
211
-
212
- ::view-transition-old(stalux-article-page),
213
- ::view-transition-new(stalux-article-page) {
214
- animation: none;
215
- }
216
-
217
- /* Article-to-article hops keep both sidebars in place; their smaller cards
218
- and content groups carry the visual change instead of sliding the columns. */
219
- html[data-stalux-vt-from="article"][data-stalux-vt-to="article"]::view-transition-group(
220
- left-sidebar
221
- ),
222
- html[data-stalux-vt-from="article"][data-stalux-vt-to="article"]::view-transition-group(
223
- right-sidebar
224
- ),
225
- html[data-stalux-vt-from="article"][data-stalux-vt-to="article"]::view-transition-old(left-sidebar),
226
- html[data-stalux-vt-from="article"][data-stalux-vt-to="article"]::view-transition-new(left-sidebar),
227
- html[data-stalux-vt-from="article"][data-stalux-vt-to="article"]::view-transition-old(
228
- right-sidebar
229
- ),
230
- html[data-stalux-vt-from="article"][data-stalux-vt-to="article"]::view-transition-new(
231
- right-sidebar
232
- ) {
233
- animation: none !important;
234
- }
235
-
236
- ::view-transition-old(stalux-page),
237
- ::view-transition-new(stalux-page) {
238
- transform-origin: center top;
239
- }
240
-
241
- ::view-transition-group(*) {
242
- animation-duration: 240ms;
243
- animation-timing-function: cubic-bezier(0.22, 0.72, 0.22, 1);
244
- }
245
-
246
- /* Persisted headers can keep a route-scoped Astro attribute after a swap. Use
247
- the stable persist marker for the shared transition name across routes. */
248
- [data-astro-transition-persist="stalux-site-header"] {
249
- view-transition-name: stalux-site-header;
250
- }
251
-
252
- /* Keep the shared header stationary while the route content animates below it. */
253
- ::view-transition-group(stalux-site-header) {
254
- z-index: 990;
255
- animation: none;
256
- }
257
-
258
- ::view-transition-old(stalux-site-header),
259
- ::view-transition-new(stalux-site-header) {
260
- animation: none;
261
- }
262
-
263
- /* Keep the shared footer stationary while the route content animates above it. */
264
- [data-astro-transition-persist="stalux-site-footer"] {
265
- view-transition-name: stalux-site-footer;
266
- }
267
-
268
- ::view-transition-group(stalux-site-footer) {
269
- animation: none;
270
- }
271
-
272
- ::view-transition-old(stalux-site-footer),
273
- ::view-transition-new(stalux-site-footer) {
274
- animation: none;
275
- }
276
-
277
186
  @media (prefers-reduced-motion: reduce) {
278
187
  ::view-transition-group(*),
279
188
  ::view-transition-old(*),